Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
10d73930d9 | ||
|
ea12c0bfd1 | ||
|
6540d12d25 | ||
|
c438a42587 | ||
|
58b091061e | ||
|
8a7df360ac | ||
|
ba7cf90315 | ||
|
8841740a2b | ||
|
dfe1255ac3 | ||
|
0fddd1735d | ||
|
f779a3f7f5 | ||
|
7015338aef | ||
|
34aa147ebe | ||
|
4d02e1da8e | ||
|
5ef3e1f32b | ||
|
23d02363ee | ||
|
3c4fe9e260 | ||
|
a594e053f5 | ||
|
f3ba47ac89 | ||
|
7d814396b7 | ||
|
47ca113385 |
@@ -19,7 +19,9 @@ We can use the templating feature of coreos-cloudinit to automate etcd configura
|
||||
coreos:
|
||||
etcd:
|
||||
name: node001
|
||||
discovery: https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
addr: $public_ipv4:4001
|
||||
peer-addr: $private_ipv4:7001
|
||||
```
|
||||
@@ -29,7 +31,7 @@ coreos:
|
||||
```
|
||||
[Service]
|
||||
Environment="ETCD_NAME=node001"
|
||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/3445fa65423d8b04df07f59fb40218f8"
|
||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||
Environment="ETCD_ADDR=203.0.113.29:4001"
|
||||
Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
|
||||
```
|
||||
@@ -69,9 +71,9 @@ coreos:
|
||||
...would be rendered to the following `/etc/oem-release`:
|
||||
|
||||
```
|
||||
ID="rackspace"
|
||||
ID=rackspace
|
||||
NAME="Rackspace Cloud Servers"
|
||||
VERSION_ID="168.0.0"
|
||||
VERSION_ID=168.0.0
|
||||
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||
```
|
||||
@@ -171,6 +173,7 @@ All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the use
|
||||
- **no-user-group**: Boolean. Skip default group creation.
|
||||
- **ssh-authorized-keys**: List of public SSH keys to authorize for this user
|
||||
- **coreos-ssh-import-github**: Authorize SSH keys from Github user
|
||||
- **coreos-ssh-import-url**: Authorize SSH keys imported from a url endpoint.
|
||||
- **system**: Create the user as a system user. No home directory will be created.
|
||||
- **no-log-init**: Boolean. Skip initialization of lastlog and faillog databases.
|
||||
|
||||
@@ -215,6 +218,41 @@ perl -e 'print crypt("password","\$6\$SALT\$") . "\n"'
|
||||
|
||||
Using a higher number of rounds will help create more secure passwords, but given enough time, password hashes can be reversed. On most RPM based distributions there is a tool called mkpasswd available in the `expect` package, but this does not handle "rounds" nor advanced hashing algorithms.
|
||||
|
||||
#### Retrieving ssh authorized keys from a GitHub user
|
||||
|
||||
Using the field `coreos-ssh-import-github` you can make coreos-cloudinit to add the public ssh keys from a GitHub user as authorized keys to a server.
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-github: elroy
|
||||
```
|
||||
|
||||
#### Retrieving ssh authorized keys from an http endpoint
|
||||
|
||||
coreos-cloudinit can also pull public SSH keys from any http endpoint that matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
|
||||
For example, if you have an installation of GitHub Enterprise, you can provide a complete url with an authentication token:
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-url: https://token:<OAUTH-TOKEN>@github-enterprise.example.com/users/elroy/keys
|
||||
```
|
||||
|
||||
You can also provide any url which response matches that json format for public keys:
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-url: https://example.com/public-keys
|
||||
```
|
||||
|
||||
### write_files
|
||||
|
||||
Inject an arbitrary set of files to the local filesystem.
|
||||
@@ -224,3 +262,16 @@ Provide a list of objects with the following attributes:
|
||||
- **content**: Data to write at the provided `path`
|
||||
- **permissions**: String representing file permissions in octal notation (i.e. '0644')
|
||||
- **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`.
|
||||
|
||||
### manage_etc_hosts
|
||||
|
||||
Have coreos-cloudinit manage your /etc/hosts file for local name resolution.
|
||||
The only supported value is "localhost" which will cause your system's hostname
|
||||
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
|
||||
infrastructure in place to resolve its own hostname, for example, when using Vagrant.
|
||||
|
||||
```
|
||||
#cloud-config
|
||||
|
||||
manage_etc_hosts: localhost
|
||||
```
|
||||
|
30
Documentation/config-drive.md
Normal file
30
Documentation/config-drive.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Distribution via Config Drive
|
||||
|
||||
CoreOS supports providing configuration data via [config drive][config-drive]
|
||||
disk images. Currently only providing a single script or cloud config file is
|
||||
supported.
|
||||
|
||||
[config-drive]: http://docs.openstack.org/user-guide/content/enable_config_drive.html#config_drive_contents
|
||||
|
||||
## Contents and Format
|
||||
|
||||
The image should be a single FAT or ISO9660 file system with the label
|
||||
`config-2` and the configuration data should be located at
|
||||
`openstack/latest/user_data`.
|
||||
|
||||
For example, to wrap up a config named `user_data` in a config drive image:
|
||||
|
||||
mkdir -p /tmp/new-drive/openstack/latest
|
||||
cp user_data /tmp/new-drive/openstack/latest/user_data
|
||||
mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
|
||||
rm -r /tmp/new-drive
|
||||
|
||||
## QEMU virtfs
|
||||
|
||||
One exception to the above, when using QEMU it is possible to skip creating an
|
||||
image and use a plain directory containing the same contents:
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-fsdev local,id=conf,security_model=none,readonly,path=/tmp/new-drive \
|
||||
-device virtio-9p-pci,fsdev=conf,mount_tag=config-2 \
|
||||
[usual qemu options here...]
|
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const version = "0.3.2"
|
||||
const version = "0.4.0+git"
|
||||
|
||||
func main() {
|
||||
var printVersion bool
|
||||
|
@@ -20,6 +20,7 @@ type CloudConfig struct {
|
||||
WriteFiles []system.File `yaml:"write_files"`
|
||||
Hostname string
|
||||
Users []system.User
|
||||
ManageEtcHosts string `yaml:"manage_etc_hosts"`
|
||||
}
|
||||
|
||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||
@@ -91,6 +92,12 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if user.SSHImportURL != "" {
|
||||
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
||||
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,5 +170,15 @@ func Apply(cfg CloudConfig, env *Environment) error {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ManageEtcHosts != "" {
|
||||
|
||||
if err := WriteEtcHosts(cfg.ManageEtcHosts, env.Root()); err != nil {
|
||||
log.Fatalf("Failed to write /etc/hosts to filesystem: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Wrote /etc/hosts file to filesystem")
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,52 +1,18 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
type GithubUserKey struct {
|
||||
Id int `json:"id"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func fetchGithubKeys(github_url string) ([]string, error) {
|
||||
res, err := http.Get(github_url)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []GithubUserKey
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, 0)
|
||||
for _, key := range data {
|
||||
keys = append(keys, key.Key)
|
||||
}
|
||||
return keys, err
|
||||
|
||||
}
|
||||
|
||||
func SSHImportGithubUser(system_user string, github_user string) error {
|
||||
url := fmt.Sprintf("https://api.github.com/users/%s/keys", github_user)
|
||||
keys, err := fetchGithubKeys(url)
|
||||
keys, err := fetchUserKeys(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key_name := fmt.Sprintf("github-%s", github_user)
|
||||
err = system.AuthorizeSSHKeys(system_user, key_name, keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return system.AuthorizeSSHKeys(system_user, key_name, keys)
|
||||
}
|
||||
|
@@ -1,48 +1,9 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloudConfigUsersGithubMarshal(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gh_res := `
|
||||
[
|
||||
{
|
||||
"id": 67057,
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBAIHAu822ggSkIHrJYvhmBceOSVjuflfQm8RbMMDNVe9relQfuPbN+nxGGTCKzPLebeOcX+Wwi77TPXWwK3BZMglfXxhABlFPsuMb63Tqp94pBYsJdx/iFj9iGo6pKoM1k8ubOcqsUnq+BR9895zRbE7MjdwkGo67+QhCEwvkwAnNAAAAFQCuddVqXLCubzqnWmeHLQE+2GFfHwAAAIBnlXW5h15ndVuwi0htF4oodVSB1KwnTWcuBK+aE1zRs76yvRb0Ws+oifumThDwB/Tec6FQuAfRKfy6piChZqsu5KvL98I+2t5yyi1td+kMvdTnVL2lW44etDKseOcozmknCOmh4Dqvhl/2MwrDAhlPaN08EEq9h3w3mXtNLWH64QAAAIBAzDOKr17llngaKIdDXh+LtXKh87+zfjlTA36/9r2uF2kYE5uApDtu9sPCkt7+YBQt7R8prADPckwAiXwVdk0xijIOpLDBmoydQJJRQ+zTMxvpQmUr/1kUOv0zb+lB657CgvN0vVTmP2swPeMvgntt3C4vw7Ab+O+MS9peOAJbbQ=="
|
||||
},
|
||||
{
|
||||
"id": 3340477,
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBANxpzIbTzKTeBRaOIdUxwwGwvDasTfU/PonhbNIuhYjc+xFGvBRTumox2F+luVAKKs4WdvA4nJXaY1OFi6DZftk5Bp4E2JaSzp8ulAzHsMexDdv6LGHGEJj/qdHAL1vHk2K89PpwRFSRZI8XRBLjvkr4ZgBKLG5ZILXPJEPP2j3lAAAAFQCtxoTnV8wy0c4grcGrQ+1sCsD7WQAAAIAqZsW2GviMe1RQrbZT0xAZmI64XRPrnLsoLxycHWlS7r6uUln2c6Ae2MB/YF0d4Kd1XZii9GHj7rrypqEo7MW8uSabhu70nmu1J8m2O3Dsr+4oJLeat9vwPsJV92IKO0jQwjKnAOHOiB9JKGeCw+NfXfogbti9/q38Q6XcS+SI5wAAAIEA1803Y2h+tOOpZXAsNIwl9mRfExWzLQ3L7knwJdznQu/6SW1H/1oyoYLebuk187Qj2UFI5qQ6AZNc49DvohWx0Cg6ABcyubNyoaCjZKWIdxVnItHWNbLe//+tyTu0I2eQwJOORsEPK5gMpf599C7wXQ//DzZOWbTWiHEX52gCTmk="
|
||||
},
|
||||
{
|
||||
"id": 5224438,
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBAPKRWdKhzGZuLAJL6M1eM51hWViMqNBC2C6lm2OqGRYLuIf1GJ391widUuSf4wQqnkR22Q9PCmAZ19XCf11wBRMnuw9I/Z3Bt5bXfc+dzFBCmHYGJ6wNSv++H9jxyMb+usmsenWOFZGNO2jN0wrJ4ay8Yt0bwtRU+VCXpuRLszMzAAAAFQDZUIuPjcfK5HLgnwZ/J3lvtvlUjQAAAIEApIkAwLuCQV5j3U6DmI/Y6oELqSUR2purFm8jo8jePFfe1t+ghikgD254/JXlhDCVgY0NLXcak+coJfGCTT23quJ7I5xdpTn/OZO2Q6Woum/bijFC/UWwQbLz0R2nU3DoHv5v6XHQZxuIG4Fsxa91S+vWjZFtI7RuYlBCZA//ANMAAACBAJO0FojzkX6IeaWLqrgu9GTkFwGFazZ+LPH5JOWPoPn1hQKuR32Uf6qNcBZcIjY7SF0P7HF5rLQd6zKZzHqqQQ92MV555NEwjsnJglYU8CaaZsfYooaGPgA1YN7RhTSAuDmUW5Hyfj5BH4NTtrzrvJxIhDoQLf31Fasjw00r4R0O"
|
||||
}
|
||||
]
|
||||
`
|
||||
fmt.Fprintln(w, gh_res)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
keys, err := fetchGithubKeys(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
expected := "ssh-dss AAAAB3NzaC1kc3MAAACBAIHAu822ggSkIHrJYvhmBceOSVjuflfQm8RbMMDNVe9relQfuPbN+nxGGTCKzPLebeOcX+Wwi77TPXWwK3BZMglfXxhABlFPsuMb63Tqp94pBYsJdx/iFj9iGo6pKoM1k8ubOcqsUnq+BR9895zRbE7MjdwkGo67+QhCEwvkwAnNAAAAFQCuddVqXLCubzqnWmeHLQE+2GFfHwAAAIBnlXW5h15ndVuwi0htF4oodVSB1KwnTWcuBK+aE1zRs76yvRb0Ws+oifumThDwB/Tec6FQuAfRKfy6piChZqsu5KvL98I+2t5yyi1td+kMvdTnVL2lW44etDKseOcozmknCOmh4Dqvhl/2MwrDAhlPaN08EEq9h3w3mXtNLWH64QAAAIBAzDOKr17llngaKIdDXh+LtXKh87+zfjlTA36/9r2uF2kYE5uApDtu9sPCkt7+YBQt7R8prADPckwAiXwVdk0xijIOpLDBmoydQJJRQ+zTMxvpQmUr/1kUOv0zb+lB657CgvN0vVTmP2swPeMvgntt3C4vw7Ab+O+MS9peOAJbbQ=="
|
||||
if keys[0] != expected {
|
||||
t.Fatalf("expected %s, got %s", expected, keys[0])
|
||||
}
|
||||
expected = "ssh-dss AAAAB3NzaC1kc3MAAACBAPKRWdKhzGZuLAJL6M1eM51hWViMqNBC2C6lm2OqGRYLuIf1GJ391widUuSf4wQqnkR22Q9PCmAZ19XCf11wBRMnuw9I/Z3Bt5bXfc+dzFBCmHYGJ6wNSv++H9jxyMb+usmsenWOFZGNO2jN0wrJ4ay8Yt0bwtRU+VCXpuRLszMzAAAAFQDZUIuPjcfK5HLgnwZ/J3lvtvlUjQAAAIEApIkAwLuCQV5j3U6DmI/Y6oELqSUR2purFm8jo8jePFfe1t+ghikgD254/JXlhDCVgY0NLXcak+coJfGCTT23quJ7I5xdpTn/OZO2Q6Woum/bijFC/UWwQbLz0R2nU3DoHv5v6XHQZxuIG4Fsxa91S+vWjZFtI7RuYlBCZA//ANMAAACBAJO0FojzkX6IeaWLqrgu9GTkFwGFazZ+LPH5JOWPoPn1hQKuR32Uf6qNcBZcIjY7SF0P7HF5rLQd6zKZzHqqQQ92MV555NEwjsnJglYU8CaaZsfYooaGPgA1YN7RhTSAuDmUW5Hyfj5BH4NTtrzrvJxIhDoQLf31Fasjw00r4R0O"
|
||||
if keys[2] != expected {
|
||||
t.Fatalf("expected %s, got %s", expected, keys[2])
|
||||
}
|
||||
|
||||
}
|
||||
func TestCloudConfigUsersGithubUser(t *testing.T) {
|
||||
|
||||
contents := `
|
||||
|
44
initialize/manage_etc_hosts.go
Normal file
44
initialize/manage_etc_hosts.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
const DefaultIpv4Address = "127.0.0.1"
|
||||
|
||||
func generateEtcHosts(option string) (out string, err error) {
|
||||
if option != "localhost" {
|
||||
return "", errors.New("Invalid option to manage_etc_hosts")
|
||||
}
|
||||
|
||||
// use the operating system hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s\n", DefaultIpv4Address, hostname), nil
|
||||
|
||||
}
|
||||
|
||||
// Write an /etc/hosts file
|
||||
func WriteEtcHosts(option string, root string) error {
|
||||
|
||||
etcHosts, err := generateEtcHosts(option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file := system.File{
|
||||
Path: path.Join(root, "etc", "hosts"),
|
||||
RawFilePermissions: "0644",
|
||||
Content: etcHosts,
|
||||
}
|
||||
|
||||
return system.WriteFile(&file)
|
||||
}
|
76
initialize/manage_etc_hosts_test.go
Normal file
76
initialize/manage_etc_hosts_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloudConfigManageEtcHosts(t *testing.T) {
|
||||
contents := `
|
||||
manage_etc_hosts: localhost
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
manageEtcHosts := cfg.ManageEtcHosts
|
||||
|
||||
if manageEtcHosts != "localhost" {
|
||||
t.Errorf("ManageEtcHosts value is %q, expected 'localhost'", manageEtcHosts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManageEtcHostsInvalidValue(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer rmdir(dir)
|
||||
|
||||
if err := WriteEtcHosts("invalid", dir); err == nil {
|
||||
t.Fatalf("WriteEtcHosts succeeded with invalid value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcHostsWrittenToDisk(t *testing.T) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create tempdir: %v", err)
|
||||
}
|
||||
defer rmdir(dir)
|
||||
|
||||
if err := WriteEtcHosts("localhost", dir); err != nil {
|
||||
t.Fatalf("WriteEtcHosts failed: %v", err)
|
||||
}
|
||||
|
||||
fullPath := path.Join(dir, "etc", "hosts")
|
||||
|
||||
fi, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to stat file: %v", err)
|
||||
}
|
||||
|
||||
if fi.Mode() != os.FileMode(0644) {
|
||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read OS hostname: %v", err)
|
||||
}
|
||||
|
||||
expect := fmt.Sprintf("%s %s\n", DefaultIpv4Address, hostname)
|
||||
|
||||
if string(contents) != expect {
|
||||
t.Fatalf("File has incorrect contents")
|
||||
}
|
||||
}
|
@@ -18,9 +18,9 @@ type OEMRelease struct {
|
||||
|
||||
func (oem *OEMRelease) String() string {
|
||||
fields := []string{
|
||||
fmt.Sprintf("ID=%q", oem.ID),
|
||||
fmt.Sprintf("ID=%s", oem.ID),
|
||||
fmt.Sprintf("VERSION_ID=%s", oem.VersionID),
|
||||
fmt.Sprintf("NAME=%q", oem.Name),
|
||||
fmt.Sprintf("VERSION_ID=%q", oem.VersionID),
|
||||
fmt.Sprintf("HOME_URL=%q", oem.HomeURL),
|
||||
fmt.Sprintf("BUG_REPORT_URL=%q", oem.BugReportURL),
|
||||
}
|
||||
|
@@ -42,9 +42,9 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
|
||||
t.Fatalf("Unable to read expected file: %v", err)
|
||||
}
|
||||
|
||||
expect := `ID="rackspace"
|
||||
expect := `ID=rackspace
|
||||
VERSION_ID=168.0.0
|
||||
NAME="Rackspace Cloud Servers"
|
||||
VERSION_ID="168.0.0"
|
||||
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||
`
|
||||
|
47
initialize/ssh_keys.go
Normal file
47
initialize/ssh_keys.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/coreos-cloudinit/system"
|
||||
)
|
||||
|
||||
type UserKey struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func SSHImportKeysFromURL(system_user string, url string) error {
|
||||
keys, err := fetchUserKeys(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key_name := fmt.Sprintf("coreos-cloudinit-%s", system_user)
|
||||
return system.AuthorizeSSHKeys(system_user, key_name, keys)
|
||||
}
|
||||
|
||||
func fetchUserKeys(url string) ([]string, error) {
|
||||
res, err := http.Get(url)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []UserKey
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, 0)
|
||||
for _, key := range data {
|
||||
keys = append(keys, key.Key)
|
||||
}
|
||||
return keys, err
|
||||
}
|
69
initialize/ssh_keys_test.go
Normal file
69
initialize/ssh_keys_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCloudConfigUsersUrlMarshal(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gh_res := `
|
||||
[
|
||||
{
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBAIHAu822ggSkIHrJYvhmBceOSVjuflfQm8RbMMDNVe9relQfuPbN+nxGGTCKzPLebeOcX+Wwi77TPXWwK3BZMglfXxhABlFPsuMb63Tqp94pBYsJdx/iFj9iGo6pKoM1k8ubOcqsUnq+BR9895zRbE7MjdwkGo67+QhCEwvkwAnNAAAAFQCuddVqXLCubzqnWmeHLQE+2GFfHwAAAIBnlXW5h15ndVuwi0htF4oodVSB1KwnTWcuBK+aE1zRs76yvRb0Ws+oifumThDwB/Tec6FQuAfRKfy6piChZqsu5KvL98I+2t5yyi1td+kMvdTnVL2lW44etDKseOcozmknCOmh4Dqvhl/2MwrDAhlPaN08EEq9h3w3mXtNLWH64QAAAIBAzDOKr17llngaKIdDXh+LtXKh87+zfjlTA36/9r2uF2kYE5uApDtu9sPCkt7+YBQt7R8prADPckwAiXwVdk0xijIOpLDBmoydQJJRQ+zTMxvpQmUr/1kUOv0zb+lB657CgvN0vVTmP2swPeMvgntt3C4vw7Ab+O+MS9peOAJbbQ=="
|
||||
},
|
||||
{
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBANxpzIbTzKTeBRaOIdUxwwGwvDasTfU/PonhbNIuhYjc+xFGvBRTumox2F+luVAKKs4WdvA4nJXaY1OFi6DZftk5Bp4E2JaSzp8ulAzHsMexDdv6LGHGEJj/qdHAL1vHk2K89PpwRFSRZI8XRBLjvkr4ZgBKLG5ZILXPJEPP2j3lAAAAFQCtxoTnV8wy0c4grcGrQ+1sCsD7WQAAAIAqZsW2GviMe1RQrbZT0xAZmI64XRPrnLsoLxycHWlS7r6uUln2c6Ae2MB/YF0d4Kd1XZii9GHj7rrypqEo7MW8uSabhu70nmu1J8m2O3Dsr+4oJLeat9vwPsJV92IKO0jQwjKnAOHOiB9JKGeCw+NfXfogbti9/q38Q6XcS+SI5wAAAIEA1803Y2h+tOOpZXAsNIwl9mRfExWzLQ3L7knwJdznQu/6SW1H/1oyoYLebuk187Qj2UFI5qQ6AZNc49DvohWx0Cg6ABcyubNyoaCjZKWIdxVnItHWNbLe//+tyTu0I2eQwJOORsEPK5gMpf599C7wXQ//DzZOWbTWiHEX52gCTmk="
|
||||
},
|
||||
{
|
||||
"id": 5224438,
|
||||
"key": "ssh-dss AAAAB3NzaC1kc3MAAACBAPKRWdKhzGZuLAJL6M1eM51hWViMqNBC2C6lm2OqGRYLuIf1GJ391widUuSf4wQqnkR22Q9PCmAZ19XCf11wBRMnuw9I/Z3Bt5bXfc+dzFBCmHYGJ6wNSv++H9jxyMb+usmsenWOFZGNO2jN0wrJ4ay8Yt0bwtRU+VCXpuRLszMzAAAAFQDZUIuPjcfK5HLgnwZ/J3lvtvlUjQAAAIEApIkAwLuCQV5j3U6DmI/Y6oELqSUR2purFm8jo8jePFfe1t+ghikgD254/JXlhDCVgY0NLXcak+coJfGCTT23quJ7I5xdpTn/OZO2Q6Woum/bijFC/UWwQbLz0R2nU3DoHv5v6XHQZxuIG4Fsxa91S+vWjZFtI7RuYlBCZA//ANMAAACBAJO0FojzkX6IeaWLqrgu9GTkFwGFazZ+LPH5JOWPoPn1hQKuR32Uf6qNcBZcIjY7SF0P7HF5rLQd6zKZzHqqQQ92MV555NEwjsnJglYU8CaaZsfYooaGPgA1YN7RhTSAuDmUW5Hyfj5BH4NTtrzrvJxIhDoQLf31Fasjw00r4R0O"
|
||||
}
|
||||
]
|
||||
`
|
||||
fmt.Fprintln(w, gh_res)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
keys, err := fetchUserKeys(ts.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
expected := "ssh-dss AAAAB3NzaC1kc3MAAACBAIHAu822ggSkIHrJYvhmBceOSVjuflfQm8RbMMDNVe9relQfuPbN+nxGGTCKzPLebeOcX+Wwi77TPXWwK3BZMglfXxhABlFPsuMb63Tqp94pBYsJdx/iFj9iGo6pKoM1k8ubOcqsUnq+BR9895zRbE7MjdwkGo67+QhCEwvkwAnNAAAAFQCuddVqXLCubzqnWmeHLQE+2GFfHwAAAIBnlXW5h15ndVuwi0htF4oodVSB1KwnTWcuBK+aE1zRs76yvRb0Ws+oifumThDwB/Tec6FQuAfRKfy6piChZqsu5KvL98I+2t5yyi1td+kMvdTnVL2lW44etDKseOcozmknCOmh4Dqvhl/2MwrDAhlPaN08EEq9h3w3mXtNLWH64QAAAIBAzDOKr17llngaKIdDXh+LtXKh87+zfjlTA36/9r2uF2kYE5uApDtu9sPCkt7+YBQt7R8prADPckwAiXwVdk0xijIOpLDBmoydQJJRQ+zTMxvpQmUr/1kUOv0zb+lB657CgvN0vVTmP2swPeMvgntt3C4vw7Ab+O+MS9peOAJbbQ=="
|
||||
if keys[0] != expected {
|
||||
t.Fatalf("expected %s, got %s", expected, keys[0])
|
||||
}
|
||||
expected = "ssh-dss AAAAB3NzaC1kc3MAAACBAPKRWdKhzGZuLAJL6M1eM51hWViMqNBC2C6lm2OqGRYLuIf1GJ391widUuSf4wQqnkR22Q9PCmAZ19XCf11wBRMnuw9I/Z3Bt5bXfc+dzFBCmHYGJ6wNSv++H9jxyMb+usmsenWOFZGNO2jN0wrJ4ay8Yt0bwtRU+VCXpuRLszMzAAAAFQDZUIuPjcfK5HLgnwZ/J3lvtvlUjQAAAIEApIkAwLuCQV5j3U6DmI/Y6oELqSUR2purFm8jo8jePFfe1t+ghikgD254/JXlhDCVgY0NLXcak+coJfGCTT23quJ7I5xdpTn/OZO2Q6Woum/bijFC/UWwQbLz0R2nU3DoHv5v6XHQZxuIG4Fsxa91S+vWjZFtI7RuYlBCZA//ANMAAACBAJO0FojzkX6IeaWLqrgu9GTkFwGFazZ+LPH5JOWPoPn1hQKuR32Uf6qNcBZcIjY7SF0P7HF5rLQd6zKZzHqqQQ92MV555NEwjsnJglYU8CaaZsfYooaGPgA1YN7RhTSAuDmUW5Hyfj5BH4NTtrzrvJxIhDoQLf31Fasjw00r4R0O"
|
||||
if keys[2] != expected {
|
||||
t.Fatalf("expected %s, got %s", expected, keys[2])
|
||||
}
|
||||
|
||||
}
|
||||
func TestCloudConfigUsersSSHImportURL(t *testing.T) {
|
||||
|
||||
contents := `
|
||||
users:
|
||||
- name: elroy
|
||||
coreos-ssh-import-url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Users) != 1 {
|
||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
||||
}
|
||||
|
||||
user := cfg.Users[0]
|
||||
|
||||
if user.Name != "elroy" {
|
||||
t.Errorf("User name is %q, expected 'elroy'", user.Name)
|
||||
}
|
||||
|
||||
if user.SSHImportURL != "https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys" {
|
||||
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ type User struct {
|
||||
PasswordHash string `yaml:"passwd"`
|
||||
SSHAuthorizedKeys []string `yaml:"ssh-authorized-keys"`
|
||||
SSHImportGithubUser string `yaml:"coreos-ssh-import-github"`
|
||||
SSHImportURL string `yaml:"coreos-ssh-import-url"`
|
||||
GECOS string `yaml:"gecos"`
|
||||
Homedir string `yaml:"homedir"`
|
||||
NoCreateHome bool `yaml:"no-create-home"`
|
||||
@@ -33,6 +34,8 @@ func CreateUser(u *User) error {
|
||||
|
||||
if u.PasswordHash != "" {
|
||||
args = append(args, "--password", u.PasswordHash)
|
||||
} else {
|
||||
args = append(args, "--password", "*")
|
||||
}
|
||||
|
||||
if u.GECOS != "" {
|
||||
|
11
units/90-configdrive.rules
Normal file
11
units/90-configdrive.rules
Normal file
@@ -0,0 +1,11 @@
|
||||
# Automatically trigger configdrive mounting.
|
||||
|
||||
ACTION!="add|change", GOTO="coreos_configdrive_end"
|
||||
|
||||
# A normal config drive. Block device formatted with iso9660 or fat
|
||||
SUBSYSTEM=="block", ENV{ID_FS_TYPE}=="iso9660|vfat", ENV{ID_FS_LABEL}=="config-2", TAG+="systemd", ENV{SYSTEMD_WANTS}+="configdrive-block.service"
|
||||
|
||||
# Addtionally support virtfs from QEMU
|
||||
SUBSYSTEM=="virtio", DRIVER=="9pnet_virtio", ATTR{mount_tag}=="config-2", TAG+="systemd", ENV{SYSTEMD_WANTS}+="configdrive-virtfs.service"
|
||||
|
||||
LABEL="coreos_configdrive_end"
|
15
units/configdrive-block.service
Normal file
15
units/configdrive-block.service
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Mount config drive
|
||||
Conflicts=configdrive-virtfs.service umount.target
|
||||
ConditionPathIsMountPoint=!/media/configdrive
|
||||
# Only mount config drive block devices automatically in virtual machines
|
||||
ConditionVirtualization=vm
|
||||
|
||||
# OpenStack defined config drive so they get to stick their name in it
|
||||
Wants=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||
Before=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=no
|
||||
ExecStart=/bin/mount -t auto -o ro,x-mount.mkdir LABEL=config-2 /media/configdrive
|
18
units/configdrive-virtfs.service
Normal file
18
units/configdrive-virtfs.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Mount config drive from virtfs
|
||||
Conflicts=configdrive-block.service umount.target
|
||||
ConditionPathIsMountPoint=!/media/configdrive
|
||||
ConditionVirtualization=vm
|
||||
|
||||
# OpenStack defined config drive so they get to stick their name in it
|
||||
Wants=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||
Before=user-cloudinit@media-configdrive-openstack-latest-user_data.service
|
||||
|
||||
# Support old style setup for now
|
||||
Wants=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
||||
Before=addon-run@media-configdrive.service addon-config@media-configdrive.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=no
|
||||
ExecStart=/bin/mount -t 9p -o trans=virtio,version=9p2000.L,x-mount.mkdir config-2 /media/configdrive
|
11
units/system-cloudinit@.service
Normal file
11
units/system-cloudinit@.service
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Load cloud-config from %f
|
||||
Requires=dbus.service
|
||||
After=dbus.service
|
||||
Before=system-config.target
|
||||
ConditionFileNotEmpty=%f
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/bin/coreos-cloudinit --from-file=%f
|
10
units/system-config.target
Normal file
10
units/system-config.target
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Load system-provided cloud configs
|
||||
|
||||
# Generate /etc/environment
|
||||
Requires=coreos-setup-environment.service
|
||||
After=coreos-setup-environment.service
|
||||
|
||||
# Load OEM cloud-config.yml
|
||||
Requires=system-cloudinit@usr-share-oem-cloud\x2dconfig.yml.service
|
||||
After=system-cloudinit@usr-share-oem-cloud\x2dconfig.yml.service
|
12
units/user-cloudinit@.service
Normal file
12
units/user-cloudinit@.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Load cloud-config from %f
|
||||
Requires=system-config.target
|
||||
After=system-config.target
|
||||
Before=user-config.target
|
||||
ConditionFileNotEmpty=%f
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
EnvironmentFile=-/etc/environment
|
||||
ExecStart=/usr/bin/coreos-cloudinit --from-file=%f
|
8
units/user-config.target
Normal file
8
units/user-config.target
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Load user-provided cloud configs
|
||||
Requires=system-config.target
|
||||
After=system-config.target
|
||||
|
||||
# Load user_data placed by coreos-install
|
||||
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
|
||||
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
|
Reference in New Issue
Block a user