Merge pull request #1323 from justincormack/gcp-metadata

Add a metadata handler for GCP which allows ssh login
This commit is contained in:
Justin Cormack 2017-03-16 19:24:43 +00:00 committed by GitHub
commit a092d4352e
8 changed files with 167 additions and 2 deletions

4
base/metadata-gcp/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dev
proc
sys
usr

View File

@ -0,0 +1,3 @@
FROM scratch
COPY . ./
CMD ["/usr/bin/metadata-gcp"]

View File

@ -0,0 +1,44 @@
GO_COMPILE=mobylinux/go-compile:3afebc59c5cde31024493c3f91e6102d584a30b9@sha256:e0786141ea7df8ba5735b63f2a24b4ade9eae5a02b0e04c4fca33b425ec69b0a
SHA_IMAGE=alpine:3.5@sha256:dfbd4a3a8ebca874ebd2474f044a0b33600d4523d03b0df76e5c5986cb02d7e8
METADATA_BINARY=usr/bin/metadata-gcp
IMAGE=metadata-gcp
.PHONY: tag push clean container
default: push
$(METADATA_BINARY): gcp.go
mkdir -p $(dir $@)
tar cf - $^ | docker run --rm --net=none --log-driver=none -i $(GO_COMPILE) -o $@ | tar xf -
DIRS=dev proc sys
$(DIRS):
mkdir -p $@
DEPS=$(DIRS) $(METADATA_BINARY)
container: Dockerfile $(DEPS)
tar cf - $^ | docker build --no-cache -t $(IMAGE):build -
hash: Dockerfile $(DEPS)
find $^ -type f | xargs cat | docker run --rm -i $(SHA_IMAGE) sha1sum - | sed 's/ .*//' > hash
push: hash container
docker pull mobylinux/$(IMAGE):$(shell cat hash) || \
(docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash) && \
docker push mobylinux/$(IMAGE):$(shell cat hash))
docker rmi $(IMAGE):build
rm -f hash
tag: hash container
docker pull mobylinux/$(IMAGE):$(shell cat hash) || \
docker tag $(IMAGE):build mobylinux/$(IMAGE):$(shell cat hash)
docker rmi $(IMAGE):build
rm -f hash
clean:
rm -rf hash $(DIRS) usr
.DELETE_ON_ERROR:

75
base/metadata-gcp/gcp.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"io/ioutil"
"log"
"net/http"
"strings"
"syscall"
"time"
)
const (
project = "http://metadata.google.internal/computeMetadata/v1/project/"
instance = "http://metadata.google.internal/computeMetadata/v1/instance/"
)
// If optional not set, will panic. Optional will allow 404
// We assume most failure cases are that this code is included in a non Google Cloud
// environment, which should generally be ok, so just fail fast.
func metadata(url string, optional bool) []byte {
var client = &http.Client{
Timeout: time.Second * 2,
}
req, err := http.NewRequest("", url, nil)
if err != nil {
log.Fatalf("http NewRequest failed: %v", err)
}
req.Header.Set("Metadata-Flavor", "Google")
resp, err := client.Do(req)
if err != nil {
// Probably not running on Google Cloud but this package included
log.Fatalf("Could not contact Google Cloud Metadata service: %v", err)
}
if optional && resp.StatusCode == 404 {
return []byte{}
}
if resp.StatusCode != 200 {
// Probably not running on Google Cloud but this package included
log.Fatalf("Google Cloud Metadata Server http error: %s", resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Failed to read http response: %v", err)
}
return body
}
func main() {
hostname := metadata(instance+"hostname", false)
err := syscall.Sethostname(hostname)
if err != nil {
log.Printf("Failed to set hostname: %v", err)
}
sshKeys := metadata(project+"attributes/sshKeys", true)
// TODO also retrieve the instance keys and respect block project keys see https://cloud.google.com/compute/docs/instances/ssh-keys
// the keys have usernames attached, but as a simplification we are going to add them all to one root file
// TODO split them into individual user files and make the ssh container construct those users
rootKeys := ""
for _, line := range strings.Split(string(sshKeys), "\n") {
parts := strings.SplitN(line, ":", 2)
// ignoring username for now
if len(parts) == 2 {
rootKeys = rootKeys + parts[1] + "\n"
}
}
err = ioutil.WriteFile("/etc/ssh/authorized_keys", []byte(rootKeys), 0600)
if err != nil {
log.Printf("Failed to write ssh keys: %v", err)
}
}

View File

@ -46,10 +46,11 @@ type MobyImage struct {
NetworkMode string `yaml:"network_mode"` NetworkMode string `yaml:"network_mode"`
Pid string Pid string
Ipc string Ipc string
Uts string
ReadOnly bool `yaml:"read_only"` ReadOnly bool `yaml:"read_only"`
} }
const riddler = "mobylinux/riddler:7d4545d8b8ac2700971a83f12a3446a76db28c14@sha256:11b7310df6482fc38aa52b419c2ef1065d7b9207c633d47554e13aa99f6c0b72" const riddler = "mobylinux/riddler:8fe62ff02b9d28767554105b55d4613db3e77429@sha256:42b7f5c81fb85f8afb17548e00e5b81cfa4818c192e1199d61850491a178b0da"
// NewConfig parses a config file // NewConfig parses a config file
func NewConfig(config []byte) (*Moby, error) { func NewConfig(config []byte) (*Moby, error) {
@ -91,6 +92,10 @@ func ConfigToRun(order int, path string, image *MobyImage) []string {
// TODO only "host" supported // TODO only "host" supported
args = append(args, "--ipc="+image.Ipc) args = append(args, "--ipc="+image.Ipc)
} }
if image.Uts != "" {
// TODO only "host" supported
args = append(args, "--uts="+image.Uts)
}
for _, bind := range image.Binds { for _, bind := range image.Binds {
args = append(args, "-v", bind) args = append(args, "-v", bind)
} }

View File

@ -17,6 +17,16 @@ system:
- /proc/sys/fs/binfmt_misc:/binfmt_misc - /proc/sys/fs/binfmt_misc:/binfmt_misc
read_only: true read_only: true
command: [/usr/bin/binfmt, -dir, /etc/binfmt.d/, -mount, /binfmt_misc] command: [/usr/bin/binfmt, -dir, /etc/binfmt.d/, -mount, /binfmt_misc]
- name: metadata-gcp
image: "mobylinux/metadata-gcp:7fc3dd5ef92e0408fb3f76048bbaae88bbb55ad9"
binds:
- /tmp:/etc/ssh
- /etc/resolv.conf:/etc/resolv.conf
read_only: true
network_mode: host
uts: host
capabilities:
- CAP_SYS_ADMIN
daemon: daemon:
- name: rngd - name: rngd
image: "mobylinux/rngd:3dad6dd43270fa632ac031e99d1947f20b22eec9@sha256:1c93c1db7196f6f71f8e300bc1d15f0376dd18e8891c8789d77c8ff19f3a9a92" image: "mobylinux/rngd:3dad6dd43270fa632ac031e99d1947f20b22eec9@sha256:1c93c1db7196f6f71f8e300bc1d15f0376dd18e8891c8789d77c8ff19f3a9a92"
@ -25,6 +35,21 @@ daemon:
oom_score_adj: -800 oom_score_adj: -800
read_only: true read_only: true
command: [/bin/tini, /usr/sbin/rngd, -f] command: [/bin/tini, /usr/sbin/rngd, -f]
- name: sshd
image: "mobylinux/sshd:3940f3fa07da1b6adab975112894b103b3c491f1"
capabilities:
- CAP_NET_BIND_SERVICE
- CAP_CHOWN
- CAP_SETUID
- CAP_SETGID
- CAP_DAC_OVERRIDE
- CAP_SYS_CHROOT
- CAP_KILL
network_mode: host
binds:
- /tmp/authorized_keys:/root/.ssh/authorized_keys
- /etc/resolv.conf:/etc/resolv.conf
pid: host
- name: nginx - name: nginx
image: "nginx:alpine" image: "nginx:alpine"
capabilities: capabilities:

View File

@ -31,6 +31,7 @@ daemon:
- CAP_SETGID - CAP_SETGID
- CAP_DAC_OVERRIDE - CAP_DAC_OVERRIDE
- CAP_SYS_CHROOT - CAP_SYS_CHROOT
- CAP_KILL
network_mode: host network_mode: host
binds: binds:
- /root/.ssh:/root/.ssh - /root/.ssh:/root/.ssh

View File

@ -30,9 +30,17 @@ docker rm $CONTAINER > /dev/null
# remove user namespaces # remove user namespaces
# --read-only sets /dev ro # --read-only sets /dev ro
# /sysfs ro unless privileged - cannot detect so will do if grant all caps # /sysfs ro unless privileged - cannot detect so will do if grant all caps
# # ipc, uts namespaces always isolated
UTS="."
IPC="."
echo $ARGS | grep -q uts=host && UTS=".linux.namespaces = (.linux.namespaces|map(select(.type!=\"uts\")))"
echo $ARGS | grep -q ipc=host && IPC=".linux.namespaces = (.linux.namespaces|map(select(.type!=\"ipc\")))"
mv config.json config.json.orig mv config.json config.json.orig
cat config.json.orig | \ cat config.json.orig | \
jq "$UTS" | \
jq "$IPC" | \
jq 'del(.process.rlimits)' | \ jq 'del(.process.rlimits)' | \
jq 'del (.linux.resources.memory.swappiness)' | \ jq 'del (.linux.resources.memory.swappiness)' | \
jq 'del(.linux.uidMappings) | del(.linux.gidMappings) | .linux.namespaces = (.linux.namespaces|map(select(.type!="user")))' | \ jq 'del(.linux.uidMappings) | del(.linux.gidMappings) | .linux.namespaces = (.linux.namespaces|map(select(.type!="user")))' | \