Skip to content

Commit

Permalink
feat(init): platform discovery (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewrynhard authored May 22, 2018
1 parent c0e7996 commit b1a7a82
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 116 deletions.
16 changes: 14 additions & 2 deletions initramfs/cmd/init/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/constants"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/mount"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/platform"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/rootfs"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/service"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/switchroot"
Expand Down Expand Up @@ -43,12 +44,23 @@ func initram() (err error) {
if err = mount.Init(constants.NewRoot); err != nil {
return
}
// Discover the platform.
log.Println("discovering the platform")
p, err := platform.NewPlatform()
if err != nil {
return
}
// Download the user data.
log.Println("downloading the user data")
data, err := userdata.Download()
log.Printf("downloading the user data for the platform: %s", p.Name())
data, err := p.UserData()
if err != nil {
return
}
log.Printf("preparing the node for the platform: %s", p.Name())
// Perform any tasks required by a particular platform.
if err = p.Prepare(data); err != nil {
return
}
// Prepare the necessary files in the rootfs.
log.Println("preparing the root filesystem")
if err = rootfs.Prepare(constants.NewRoot, data); err != nil {
Expand Down
39 changes: 39 additions & 0 deletions initramfs/cmd/init/pkg/platform/baremetal/baremetal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// +build linux

package baremetal

import (
"fmt"

"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/constants"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/kernel"
"github.com/autonomy/dianemo/initramfs/pkg/userdata"
)

// BareMetal is a discoverer for non-cloud environments.
type BareMetal struct{}

// Name implements the platform.Platform interface.
func (b *BareMetal) Name() string {
return "Bare Metal"
}

// UserData implements the platform.Platform interface.
func (b *BareMetal) UserData() (data userdata.UserData, err error) {
arguments, err := kernel.ParseProcCmdline()
if err != nil {
return
}

endpoint, ok := arguments[constants.KernelParamUserData]
if !ok {
return data, fmt.Errorf("no user data endpoint was found")
}

return userdata.Download(endpoint)
}

// Prepare implements the platform.Platform interface.
func (b *BareMetal) Prepare(data userdata.UserData) (err error) {
return nil
}
149 changes: 149 additions & 0 deletions initramfs/cmd/init/pkg/platform/cloud/aws/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// +build linux

package aws

import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"

"github.com/autonomy/dianemo/initramfs/pkg/userdata"
"github.com/fullsailor/pkcs7"
"golang.org/x/sys/unix"
)

const (
// AWSUserDataEndpoint is the local EC2 endpoint for the user data.
AWSUserDataEndpoint = "http://169.254.169.254/latest/user-data"
// AWSPKCS7Endpoint is the local EC2 endpoint for the PKCS7 signature.
AWSPKCS7Endpoint = "http://169.254.169.254/latest/dynamic/instance-identity/pkcs7"
// AWSHostnameEndpoint is the local EC2 endpoint for the hostname.
AWSHostnameEndpoint = "http://169.254.169.254/latest/meta-data/hostname"
// AWSPublicCertificate is the AWS public certificate for the regions
// provided by an AWS account.
AWSPublicCertificate = `-----BEGIN CERTIFICATE-----
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
-----END CERTIFICATE-----`
)

// AWS is the concrete type that implements the platform.Platform interface.
type AWS struct{}

// IsEC2 uses the EC2 PKCS7 signature to verify the instance by validating it
// against the appropriate AWS public certificate. See
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
func IsEC2() (b bool) {
resp, err := http.Get(AWSPKCS7Endpoint)
if err != nil {
return
}
// nolint: errcheck
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
fmt.Printf("failed to download PKCS7 signature: %d\n", resp.StatusCode)
return
}

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}

data = append([]byte("-----BEGIN PKCS7-----\n"), data...)
data = append(data, []byte("\n-----END PKCS7-----\n")...)

pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
log.Println("failed to decode PEM block")
return
}

p7, err := pkcs7.Parse(pemBlock.Bytes)
if err != nil {
log.Printf("failed to parse PKCS7 signature: %v\n", err)
return
}

pemBlock, _ = pem.Decode([]byte(AWSPublicCertificate))
if pemBlock == nil {
log.Println("failed to decode PEM block")
return
}

certificate, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
log.Printf("failed to parse X509 certificate: %v\n", err)
return
}

p7.Certificates = []*x509.Certificate{certificate}

err = p7.Verify()
if err != nil {
log.Printf("failed to verify PKCS7 signature: %v", err)
return
}

b = true

return b
}

// Name implements the platform.Platform interface.
func (a *AWS) Name() string {
return "AWS"
}

// UserData implements the platform.Platform interface.
func (a *AWS) UserData() (userdata.UserData, error) {
return userdata.Download(AWSUserDataEndpoint)
}

// Prepare implements the platform.Platform interface.
func (a *AWS) Prepare(data userdata.UserData) (err error) {
return hostname()
}

func hostname() (err error) {
resp, err := http.Get(AWSHostnameEndpoint)
if err != nil {
return
}
// nolint: errcheck
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download user data: %d", resp.StatusCode)
}

dataBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}

if err = unix.Sethostname(dataBytes); err != nil {
return
}

return nil
}
27 changes: 27 additions & 0 deletions initramfs/cmd/init/pkg/platform/platform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// +build linux

package platform

import (
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/platform/baremetal"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/platform/cloud/aws"
"github.com/autonomy/dianemo/initramfs/pkg/userdata"
)

// Platform is an interface describing a platform.
type Platform interface {
Name() string
UserData() (userdata.UserData, error)
Prepare(userdata.UserData) error
}

// NewPlatform is a helper func for discovering the current platform.
func NewPlatform() (p Platform, err error) {
if aws.IsEC2() {
p = &aws.AWS{}
} else {
p = &baremetal.BareMetal{}
}

return p, nil
}
115 changes: 1 addition & 114 deletions initramfs/pkg/userdata/userdata.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,14 @@
package userdata

import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"

"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/constants"
"github.com/autonomy/dianemo/initramfs/cmd/init/pkg/kernel"
"github.com/fullsailor/pkcs7"
yaml "gopkg.in/yaml.v2"
)

const (
// AWSUserDataEndpoint is the local EC2 endpoint for the user data.
AWSUserDataEndpoint = "http://169.254.169.254/latest/user-data"
// AWSPKCS7Endpoint is the local EC2 endpoint for the PKCS7 signature.
AWSPKCS7Endpoint = "http://169.254.169.254/latest/dynamic/instance-identity/pkcs7"
// AWSPublicCertificate is the AWS public certificate for the regions
// provided by an AWS account.
AWSPublicCertificate = `-----BEGIN CERTIFICATE-----
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
-----END CERTIFICATE-----`
)

// UserData represents the user data.
type UserData struct {
Version string `yaml:"version"`
Expand Down Expand Up @@ -148,25 +115,7 @@ func (p *PEMEncodedCertificateAndKey) MarshalYAML() (interface{}, error) {
}

// Download initializes a UserData struct from a remote URL.
func Download() (data UserData, err error) {
var url string

arguments, err := kernel.ParseProcCmdline()
if err != nil {
return data, fmt.Errorf("parse kernel parameters: %s", err.Error())
}
url, ok := arguments[constants.KernelParamUserData]
if !ok {
if IsEC2() {
url = AWSUserDataEndpoint
goto L
}

return data, fmt.Errorf("no user data was found")
}

L:

func Download(url string) (data UserData, err error) {
resp, err := http.Get(url)
if err != nil {
return
Expand Down Expand Up @@ -209,65 +158,3 @@ func Open(p string) (data *UserData, err error) {

return data, nil
}

// IsEC2 uses the EC2 PKCS7 signature to verify the instance by validating it
// against the appropriate AWS public certificate. See
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
func IsEC2() (b bool) {
resp, err := http.Get(AWSPKCS7Endpoint)
if err != nil {
return
}
// nolint: errcheck
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
fmt.Printf("failed to download PKCS7 signature: %d\n", resp.StatusCode)
return
}

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}

data = append([]byte("-----BEGIN PKCS7-----\n"), data...)
data = append(data, []byte("\n-----END PKCS7-----\n")...)

pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
log.Println("failed to decode PEM block")
return
}

p7, err := pkcs7.Parse(pemBlock.Bytes)
if err != nil {
log.Printf("failed to parse PKCS7 signature: %v\n", err)
return
}

pemBlock, _ = pem.Decode([]byte(AWSPublicCertificate))
if pemBlock == nil {
log.Println("failed to decode PEM block")
return
}

certificate, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
log.Printf("failed to parse X509 certificate: %v\n", err)
return
}

p7.Certificates = []*x509.Certificate{certificate}

err = p7.Verify()
if err != nil {
log.Printf("failed to verify PKCS7 signature: %v", err)
return
}

b = true

return b
}

0 comments on commit b1a7a82

Please sign in to comment.