Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
bin/
coverage/
gopath/
/coreos-cloudinit
3 changes: 2 additions & 1 deletion Documentation/cloud-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,11 @@ coreos:

### ssh_authorized_keys

The `ssh_authorized_keys` parameter adds public SSH keys which will be authorized for the `core` user.
The `ssh_authorized_keys` parameter adds public SSH keys which will be authorized for the default user (defaults to `core`).

The keys will be named "coreos-cloudinit" by default.
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
Change the default user by using the `--default-user` flag.

```yaml
#cloud-config
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ write_files:
nameserver 192.0.2.3
```

By default, SSH keys from `ssh_authorized_keys` and user-data are applied to the `core` user.
This can be customized using the `--default-user` flag when running `coreos-cloudinit`.

## Executing a Script

coreos-cloudinit supports executing user-data as a script instead of parsing it as a cloud-config document.
Expand Down
6 changes: 4 additions & 2 deletions coreos-cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ var (
convertNetconf string
workspace string
sshKeyName string
defaultUser string
oem string
validate bool
}{}
Expand All @@ -98,6 +99,7 @@ func init() {
flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
flag.StringVar(&flags.sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
flag.StringVar(&flags.defaultUser, "default-user", initialize.DefaultUser, "Default user to receive SSH keys from cloud-config")
flag.BoolVar(&flags.validate, "validate", false, "[EXPERIMENTAL] Validate the user-data but do not apply it to the system")
}

Expand Down Expand Up @@ -184,7 +186,7 @@ func main() {
log.Printf("Failed fetching meta-data from datasource: %v\n", err)
os.Exit(1)
}
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, flags.defaultUser, metadata)

// Setup networking units
if flags.convertNetconf != "" {
Expand Down Expand Up @@ -236,7 +238,7 @@ func main() {
}

mergedKeys := mergeSSHKeysFromSources(metadata, udata)
if err := initialize.ApplyCoreUserSSHKeys(mergedKeys, env); err != nil {
if err := initialize.ApplyDefaultUserSSHKeys(mergedKeys, env); err != nil {
log.Printf("Failed to apply SSH keys: %v", err)
mustStop = true
}
Expand Down
10 changes: 5 additions & 5 deletions initialize/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ func ApplyHostname(hostname string) error {
return nil
}

func ApplyCoreUserSSHKeys(keys []string, env *Environment) error {
func ApplyDefaultUserSSHKeys(keys []string, env *Environment) error {
if len(keys) == 0 {
return nil
}
err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), keys)
err := system.AuthorizeSSHKeys(env.DefaultUser(), env.SSHKeyName(), keys)
if err != nil {
return err
}
log.Printf("Authorized SSH keys for core user")
log.Printf("Authorized SSH keys for %s user", env.DefaultUser())
return nil
}

Expand Down Expand Up @@ -107,9 +107,9 @@ func Apply(cfg config.CloudConfig, env *Environment) error {
}
}

// If this is the "core" user, we skip adding the ssh keys from SSHAuthorizedKeys. Those keys will
// If this is the default user, we skip adding the ssh keys from SSHAuthorizedKeys. Those keys will
// be added later along any other keys we fetch from user-data and metadata.
if len(user.SSHAuthorizedKeys) > 0 && user.Name != "core" {
if len(user.SSHAuthorizedKeys) > 0 && user.Name != env.DefaultUser() {
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
return err
Expand Down
10 changes: 8 additions & 2 deletions initialize/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ import (
)

const DefaultSSHKeyName = "coreos-cloudinit"
const DefaultUser = "core"

type Environment struct {
root string
configRoot string
workspace string
sshKeyName string
defaultUser string
substitutions map[string]string
}

// TODO(jonboulle): this is getting unwieldy, should be able to simplify the interface somehow
func NewEnvironment(root, configRoot, workspace, sshKeyName string, metadata datasource.Metadata) *Environment {
func NewEnvironment(root, configRoot, workspace, sshKeyName, defaultUser string, metadata datasource.Metadata) *Environment {
firstNonNull := func(ip net.IP, env string) string {
if ip == nil {
return env
Expand All @@ -50,7 +52,7 @@ func NewEnvironment(root, configRoot, workspace, sshKeyName string, metadata dat
"$public_ipv6": firstNonNull(metadata.PublicIPv6, os.Getenv("COREOS_PUBLIC_IPV6")),
"$private_ipv6": firstNonNull(metadata.PrivateIPv6, os.Getenv("COREOS_PRIVATE_IPV6")),
}
return &Environment{root, configRoot, workspace, sshKeyName, substitutions}
return &Environment{root, configRoot, workspace, sshKeyName, defaultUser, substitutions}
}

func (e *Environment) Workspace() string {
Expand All @@ -73,6 +75,10 @@ func (e *Environment) SetSSHKeyName(name string) {
e.sshKeyName = name
}

func (e *Environment) DefaultUser() string {
return e.defaultUser
}

// Apply goes through the map of substitutions and replaces all instances of
// the keys with their respective values. It supports escaping substitutions
// with a leading '\'.
Expand Down
22 changes: 19 additions & 3 deletions initialize/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ addr: $private_ipv4
},
} {

env := NewEnvironment("./", "./", "./", "", tt.metadata)
env := NewEnvironment("./", "./", "./", "", DefaultUser, tt.metadata)
got := env.Apply(tt.input)
if got != tt.out {
t.Fatalf("Environment incorrectly applied.\ngot:\n%s\nwant:\n%s", got, tt.out)
Expand All @@ -118,7 +118,7 @@ func TestEnvironmentFile(t *testing.T) {
}
defer os.RemoveAll(dir)

env := NewEnvironment("./", "./", "./", "", metadata)
env := NewEnvironment("./", "./", "./", "", DefaultUser, metadata)
ef := env.DefaultEnvironmentFile()
err = system.WriteEnvFile(ef, dir)
if err != nil {
Expand All @@ -140,9 +140,25 @@ func TestEnvironmentFileNil(t *testing.T) {
os.Clearenv()
metadata := datasource.Metadata{}

env := NewEnvironment("./", "./", "./", "", metadata)
env := NewEnvironment("./", "./", "./", "", DefaultUser, metadata)
ef := env.DefaultEnvironmentFile()
if ef != nil {
t.Fatalf("Environment file not nil: %v", ef)
}
}

func TestEnvironmentDefaultUser(t *testing.T) {
metadata := datasource.Metadata{}

// Test default value
env := NewEnvironment("./", "./", "./", "", DefaultUser, metadata)
if env.DefaultUser() != "core" {
t.Fatalf("Expected default user 'core', got '%s'", env.DefaultUser())
}

// Test custom value
env = NewEnvironment("./", "./", "./", "", "admin", metadata)
if env.DefaultUser() != "admin" {
t.Fatalf("Expected default user 'admin', got '%s'", env.DefaultUser())
}
}
15 changes: 15 additions & 0 deletions initialize/testdata/custom_user_cloudconfig.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#cloud-config

hostname: customhost

ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGdByTgSVHq global-key

users:
- name: admin
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCaLEE+pLP admin-key-1
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCf0HE7V4C admin-key-2
- name: core
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd6H4rOzC core-key
6 changes: 3 additions & 3 deletions initialize/user_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,12 +356,12 @@ func (ud *UserData) FindSSHKeys(additionalKeys []string) []string {

if part.cloudConfig.Users != nil {
for _, user := range part.cloudConfig.Users {
if user.Name != "core" {
if user.Name != ud.env.DefaultUser() {
continue
}

// The "core" user is the default user on coreos systems. Append these keys to the list.
// TODO(gabriel-samfira): make the default user configurable?
// The default user on the system receives SSH keys from cloud-config.
// The default user is configurable via the --default-user flag.
for _, key := range user.SSHAuthorizedKeys {
keys[key] = struct{}{}
}
Expand Down
54 changes: 53 additions & 1 deletion initialize/user_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestParseMultipartMime(t *testing.T) {

func getTestEnv() *Environment {
metadata := datasource.Metadata{}
return NewEnvironment("./", "./", "./", "", metadata)
return NewEnvironment("./", "./", "./", "", DefaultUser, metadata)
}

func TestNewUserDataParsesIgnition(t *testing.T) {
Expand Down Expand Up @@ -112,3 +112,55 @@ func TestNewUserDataParsesUnknown(t *testing.T) {
require.Equal(t, 1, len(udata.Parts))
require.Equal(t, udata.Parts[0].userDataType, UnknownType)
}

func TestFindSSHKeysWithDefaultUser(t *testing.T) {
data, err := os.ReadFile("testdata/custom_user_cloudconfig.txt")
require.NoError(t, err)

// Test with default user = "core"
env := getTestEnv()
udata, err := NewUserData(string(data), env)
require.NoError(t, err)

keys := udata.FindSSHKeys([]string{})
require.Equal(t, 2, len(keys))
// Should find global key and core user's key
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGdByTgSVHq global-key")
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd6H4rOzC core-key")
}

func TestFindSSHKeysWithCustomDefaultUser(t *testing.T) {
data, err := os.ReadFile("testdata/custom_user_cloudconfig.txt")
require.NoError(t, err)

// Test with default user = "admin"
metadata := datasource.Metadata{}
env := NewEnvironment("./", "./", "./", "", "admin", metadata)
udata, err := NewUserData(string(data), env)
require.NoError(t, err)

keys := udata.FindSSHKeys([]string{})
require.Equal(t, 3, len(keys))
// Should find global key and admin user's two keys
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGdByTgSVHq global-key")
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCaLEE+pLP admin-key-1")
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCf0HE7V4C admin-key-2")
// Should NOT find core user's key
for _, key := range keys {
require.NotEqual(t, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDd6H4rOzC core-key", key)
}
}

func TestFindSSHKeysWithAdditionalKeys(t *testing.T) {
data, err := os.ReadFile("testdata/custom_user_cloudconfig.txt")
require.NoError(t, err)

env := getTestEnv()
udata, err := NewUserData(string(data), env)
require.NoError(t, err)

additionalKeys := []string{"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC additional-key"}
keys := udata.FindSSHKeys(additionalKeys)
require.Equal(t, 3, len(keys))
require.Contains(t, keys, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC additional-key")
}