diff --git a/config/config_test.go b/config/config_test.go old mode 100644 new mode 100755 diff --git a/config/data_funcs.go b/config/data_funcs.go old mode 100644 new mode 100755 diff --git a/config/disk.go b/config/disk.go old mode 100644 new mode 100755 index 717dd018..c367012e --- a/config/disk.go +++ b/config/disk.go @@ -5,6 +5,7 @@ import ( "os" "path" "path/filepath" + "reflect" "sort" "strings" @@ -48,6 +49,7 @@ func loadRawDiskConfig(dirPrefix string, full bool) map[interface{}]interface{} func loadRawConfig(dirPrefix string, full bool) map[interface{}]interface{} { rawCfg := loadRawDiskConfig(dirPrefix, full) rawCfg = util.Merge(rawCfg, readCmdline()) + rawCfg = util.Merge(rawCfg, readElidedCmdline(rawCfg)) rawCfg = applyDebugFlags(rawCfg) return mergeMetadata(rawCfg, readMetadata()) } @@ -69,6 +71,35 @@ func LoadConfigWithPrefix(dirPrefix string) *CloudConfig { return cfg } +func Insert(m interface{}, args ...interface{}) interface{} { + // TODO: move to util.go + if len(args)%2 != 0 { + panic("must have pairs of keys and values") + } + mv := reflect.ValueOf(m) + if mv.IsNil() { + mv = reflect.MakeMap(mv.Type()) + } + for i := 0; i < len(args); i += 2 { + mv.SetMapIndex(reflect.ValueOf(args[i]), reflect.ValueOf(args[i+1])) + } + return mv.Interface() +} + +func SaveInitCmdline(cmdLineArgs string) { + elidedCfg := parseCmdline(cmdLineArgs) + + env := Insert(make(map[interface{}]interface{}), interface{}("EXTRA_CMDLINE"), interface{}(cmdLineArgs)) + rancher := Insert(make(map[interface{}]interface{}), interface{}("environment"), env) + newCfg := Insert(elidedCfg, interface{}("rancher"), rancher) + // make it easy for readElidedCmdline(rawCfg) + newCfg = Insert(newCfg, interface{}("EXTRA_CMDLINE"), interface{}(cmdLineArgs)) + + if err := WriteToFile(newCfg, CloudConfigInitFile); err != nil { + log.Errorf("Failed to write init-cmdline config: %s", err) + } +} + func CloudConfigDirFiles(dirPrefix string) []string { cloudConfigDir := path.Join(dirPrefix, CloudConfigDir) @@ -160,6 +191,20 @@ func readMetadata() datasource.Metadata { return metadata } +func readElidedCmdline(rawCfg map[interface{}]interface{}) map[interface{}]interface{} { + + for k, v := range rawCfg { + if key, _ := k.(string); key == "EXTRA_CMDLINE" { + if val, ok := v.(string); ok { + cmdLineObj := parseCmdline(strings.TrimSpace(util.UnescapeKernelParams(string(val)))) + + return cmdLineObj + } + } + } + return nil +} + func readCmdline() map[interface{}]interface{} { cmdLine, err := ioutil.ReadFile("/proc/cmdline") if err != nil { @@ -230,6 +275,7 @@ func readConfigs(bytes []byte, substituteMetadataVars, returnErr bool, files ... left := make(map[interface{}]interface{}) metadata := readMetadata() for _, file := range files { + //os.Stderr.WriteString(fmt.Sprintf("READCONFIGS(%s)", file)) content, err := readConfigFile(file) if err != nil { if returnErr { diff --git a/config/types.go b/config/types.go index db9267c9..683c319c 100755 --- a/config/types.go +++ b/config/types.go @@ -40,6 +40,7 @@ const ( OsConfigFile = "/usr/share/ros/os-config.yml" VarRancherDir = "/var/lib/rancher" CloudConfigDir = "/var/lib/rancher/conf/cloud-config.d" + CloudConfigInitFile = "/var/lib/rancher/conf/cloud-config.d/init.yml" CloudConfigBootFile = "/var/lib/rancher/conf/cloud-config.d/boot.yml" CloudConfigNetworkFile = "/var/lib/rancher/conf/cloud-config.d/network.yml" CloudConfigScriptFile = "/var/lib/rancher/conf/cloud-config-script" @@ -85,9 +86,9 @@ type Repository struct { type Repositories map[string]Repository type CloudConfig struct { - SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` - WriteFiles []File `yaml:"write_files"` - Hostname string `yaml:"hostname"` + SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys,omitempty"` + WriteFiles []File `yaml:"write_files,omitempty"` + Hostname string `yaml:"hostname,omitempty"` Mounts [][]string `yaml:"mounts,omitempty"` Rancher RancherConfig `yaml:"rancher,omitempty"` Runcmd []yaml.StringandSlice `yaml:"runcmd,omitempty"` diff --git a/docs/os/running-rancheros/server/pxe/index.md b/docs/os/running-rancheros/server/pxe/index.md index c773968d..ae8753d6 100644 --- a/docs/os/running-rancheros/server/pxe/index.md +++ b/docs/os/running-rancheros/server/pxe/index.md @@ -18,7 +18,35 @@ initrd ${base-url}/initrd boot ``` -### Datasources +### Hiding sensitive kernel commandline parameters + +From RancherOS v0.9.0, secrets can be put on the `kernel` parameters line afer a `--` double dash, and they will be not be shown in any `/proc/cmdline`. These parameters +will be passed to the RancherOS init process and stored in the `root` accessible `/var/lib/rancher/conf/cloud-init.d/init.yml` file, and are available to the root user from the `ros config` commands. + +For example, the `kernel` line above could be written as: + +``` +kernel ${base-url}/vmlinuz rancher.state.dev=LABEL=RANCHER_STATE rancher.state.autoformat=[/dev/sda] -- rancher.cloud_init.datasources=[url:http://example.com/cloud-config] +``` + +The hidden part of the command line can be accessed with either `sudo ros config get rancher.environment.EXTRA_CMDLINE`, or by using a service file's environment array. + +An example service.yml file: + +``` +test: + image: alpine + command: echo "tell me a secret ${EXTRA_CMDLINE}" + labels: + io.rancher.os.scope: system + environment: + - EXTRA_CMDLINE +``` + +When this service is run, the `EXTRA_CMDLINE` will be set. + + +### cloud-init Datasources Valid [datasources](https://github.com/rancher/os/blob/3338c4ac63597940bcde7e6005f1cc09287062a2/cmd/cloudinit/cloudinit.go#L378) for RancherOS. diff --git a/docs/os/upgrading/index.md b/docs/os/upgrading/index.md index ca43b9b3..892c7665 100644 --- a/docs/os/upgrading/index.md +++ b/docs/os/upgrading/index.md @@ -9,7 +9,7 @@ layout: os-default If RancherOS has released a new version and you want to learn how to upgrade your OS, we make it easy using the `ros os` command. -Since RancherOS is a kernel and initrd, the upgrade process is downloading a new kernel and initrd, and updating the boot loader to point to it. The old kernel and initrd are not removed. If there is a problem with your upgrade, you can select the old kernel from the bootloader, which is grub2 by default. +Since RancherOS is a kernel and initrd, the upgrade process is downloading a new kernel and initrd, and updating the boot loader to point to it. The old kernel and initrd are not removed. If there is a problem with your upgrade, you can select the old kernel from the Syslinux bootloader. To see all of our releases, please visit our [releases page](https://github.com/rancher/os/releases) in GitHub. diff --git a/init/init.go b/init/init.go index 47594b28..4b61ee6f 100755 --- a/init/init.go +++ b/init/init.go @@ -227,6 +227,13 @@ func RunInit() error { func(c *config.CloudConfig) (*config.CloudConfig, error) { return c, dfs.PrepareFs(&mountConfig) }, + func(c *config.CloudConfig) (*config.CloudConfig, error) { + // will this be passed to cloud-init-save? + cmdLineArgs := strings.Join(os.Args, " ") + config.SaveInitCmdline(cmdLineArgs) + + return c, nil + }, mountOem, func(_ *config.CloudConfig) (*config.CloudConfig, error) { cfg := config.LoadConfig() @@ -303,6 +310,7 @@ func RunInit() error { }, func(cfg *config.CloudConfig) (*config.CloudConfig, error) { filesToCopy := []string{ + config.CloudConfigInitFile, config.CloudConfigBootFile, config.CloudConfigNetworkFile, config.MetaDataFile, diff --git a/scripts/build-images b/scripts/build-images index a3d677c2..8353870e 100755 --- a/scripts/build-images +++ b/scripts/build-images @@ -14,7 +14,7 @@ touch dist/images for i in $BASE/[0-9]*; do name="os-$(echo ${i} | cut -f2 -d-)" tag="${OS_REPO}/${name}:${VERSION}${SUFFIX}" - echo Building ${tag} + echo "build-image: Building ${tag}" if [ -x ${i}/prebuild.sh ]; then ${i}/prebuild.sh fi @@ -28,3 +28,5 @@ for i in $BASE/[0-9]*; do echo "WARN: Skipping ${tag}" fi done + +echo "build-image: DONE" diff --git a/scripts/run b/scripts/run index 5b401d10..5c008005 100755 --- a/scripts/run +++ b/scripts/run @@ -29,6 +29,10 @@ while [ "$#" -gt 0 ]; do shift 1 QEMU_APPEND="${QEMU_APPEND} $1" ;; + --append-init) + shift 1 + APPEND_INIT="${APPEND_INIT} $1" + ;; --memory) shift 1 MEMORY="$1" @@ -125,6 +129,9 @@ fi if [ "$RM_USR" == "1" ]; then KERNEL_ARGS="${KERNEL_ARGS} rancher.rm_usr" fi +if [ "$APPEND_INIT" != "" ]; then + KERNEL_ARGS="${KERNEL_ARGS} -- ${APPEND_INIT}" +fi if [ "$BOOT_PXE" == "1" ]; then sudo pixiecore boot \ diff --git a/scripts/tar-images b/scripts/tar-images index 93b3afdd..dc1b0563 100755 --- a/scripts/tar-images +++ b/scripts/tar-images @@ -4,10 +4,14 @@ set -e cd $(dirname $0)/.. IMAGES=$(bin/host_ros c images -i build/initrd/usr/share/ros/os-config.yml) +echo "tar-image: IMAGES=$IMAGES" for i in $IMAGES; do + echo "tar-image: pull($i)" if [ "${FORCE_PULL}" = "1" ] || ! docker inspect $i >/dev/null 2>&1; then docker pull $i fi done +echo "tar-images: docker save ${IMAGES} > build/images.tar" docker save ${IMAGES} > build/images.tar +echo "tar-images: DONE" diff --git a/tests/cmdline_test.go b/tests/cmdline_test.go new file mode 100755 index 00000000..fd007c24 --- /dev/null +++ b/tests/cmdline_test.go @@ -0,0 +1,58 @@ +package integration + +import . "gopkg.in/check.v1" +import "fmt" + +func (s *QemuSuite) TestElideCmdLine(c *C) { + extra := "cc.hostname=nope rancher.password=three" + runArgs := []string{ + "--fresh", + "--append", + "cc.something=yes rancher.password=two", + "--append-init", + extra, + } + s.RunQemuWith(c, runArgs...) + + s.CheckOutput(c, "nope\n", Equals, "hostname") + s.CheckOutput(c, + "printk.devkmsg=on rancher.debug=true rancher.password=rancher console=ttyS0 rancher.autologin=ttyS0 cc.something=yes rancher.password=two rancher.state.dev=LABEL=RANCHER_STATE rancher.state.autoformat=[/dev/sda,/dev/vda] rancher.rm_usr -- \n", + Equals, + "cat /proc/cmdline", + ) + s.CheckOutput(c, + fmt.Sprintf("/init %s\n", extra), + Equals, + "sudo ros config get rancher.environment.EXTRA_CMDLINE", + ) + // TODO: it seems that rancher.password and rancher.autologin are in `ros config export`, but accessible as `ros config get` + s.CheckOutput(c, "\n", Equals, "sudo ros config get rancher.password") + s.CheckOutput(c, + "EXTRA_CMDLINE: /init cc.hostname=nope rancher.password=three\n"+ + " EXTRA_CMDLINE: /init cc.hostname=nope rancher.password=three\n"+ + " password: three\n", + Equals, + "sudo ros config export | grep password", + ) + + // And then add a service.yml file example. + s.CheckCall(c, + `echo 'test: + image: alpine + command: echo "tell me a secret ${EXTRA_CMDLINE}" + labels: + io.rancher.os.scope: system + environment: + - EXTRA_CMDLINE +' > test.yml`) + s.CheckCall(c, "sudo mv test.yml /var/lib/rancher/conf/test.yml") + s.CheckCall(c, "sudo ros service enable /var/lib/rancher/conf/test.yml") + s.CheckCall(c, "sudo ros service up test") + s.CheckOutput(c, + "test_1 | tell me a secret /init cc.hostname=nope rancher.password=three\n", + Equals, + "sudo ros service logs test | grep secret", + ) + + // TODO: add a test showing we have the right password set +}