From 42bf3ee85b4089902d642430a054060ae0597a33 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Tue, 15 Jun 2021 23:27:55 -0700 Subject: [PATCH] Seed the random number generator on AWS --- nodeup/pkg/model/bootstrap_client.go | 6 +-- pkg/apis/nodeup/config.go | 5 +- pkg/model/iam/iam_builder.go | 14 +++++ upup/pkg/fi/cloudup/apply_cluster.go | 6 +-- upup/pkg/fi/nodeup/BUILD.bazel | 1 + upup/pkg/fi/nodeup/command.go | 80 ++++++++++++++++++++++------ 6 files changed, 86 insertions(+), 26 deletions(-) diff --git a/nodeup/pkg/model/bootstrap_client.go b/nodeup/pkg/model/bootstrap_client.go index 4c718b6002..5bf418e327 100644 --- a/nodeup/pkg/model/bootstrap_client.go +++ b/nodeup/pkg/model/bootstrap_client.go @@ -43,11 +43,7 @@ func (b BootstrapClientBuilder) Build(c *fi.ModelBuilderContext) error { var err error switch kops.CloudProviderID(b.Cluster.Spec.CloudProvider) { case kops.CloudProviderAWS: - region, regionErr := awsup.FindRegion(b.Cluster) - if regionErr != nil { - return fmt.Errorf("querying AWS region: %v", regionErr) - } - authenticator, err = awsup.NewAWSAuthenticator(region) + authenticator, err = awsup.NewAWSAuthenticator(b.Cloud.Region()) default: return fmt.Errorf("unsupported cloud provider %s", b.Cluster.Spec.CloudProvider) } diff --git a/pkg/apis/nodeup/config.go b/pkg/apis/nodeup/config.go index 674aadcf8a..e4e369dbc6 100644 --- a/pkg/apis/nodeup/config.go +++ b/pkg/apis/nodeup/config.go @@ -43,6 +43,8 @@ type Config struct { InstanceGroupRole kops.InstanceGroupRole // ClusterName is the name of the cluster ClusterName string `json:",omitempty"` + // CloudProvider is the cloud provider in use. + CloudProvider string // Channels is a list of channels that we should apply Channels []string `json:"channels,omitempty"` // ApiserverAdditionalIPs are additional IP address to put in the apiserver server cert. @@ -91,9 +93,6 @@ type ConfigServerOptions struct { Server string `json:"server,omitempty"` // CA is the ca-certificate to require for the configuration server CA string `json:"ca,omitempty"` - - // CloudProvider is the cloud provider in use (needed for authentication) - CloudProvider string `json:"cloudProvider,omitempty"` } // Image is a docker image we should pre-load diff --git a/pkg/model/iam/iam_builder.go b/pkg/model/iam/iam_builder.go index 83542a0549..27f0603cec 100644 --- a/pkg/model/iam/iam_builder.go +++ b/pkg/model/iam/iam_builder.go @@ -247,6 +247,7 @@ func (r *NodeRoleAPIServer) BuildAWSPolicy(b *PolicyBuilder) (*Policy, error) { addMasterEC2Policies(p, resource, b.Cluster.GetName()) addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.warmPool) addCertIAMPolicies(p, resource) + addKMSGenerateRandomPolicies(p) var err error if p, err = b.AddS3Permissions(p); err != nil { @@ -293,6 +294,7 @@ func (r *NodeRoleMaster) BuildAWSPolicy(b *PolicyBuilder) (*Policy, error) { addMasterASPolicies(p, resource, b.Cluster.GetName()) addMasterELBPolicies(p, resource) addCertIAMPolicies(p, resource) + addKMSGenerateRandomPolicies(p) var err error if p, err = b.AddS3Permissions(p); err != nil { @@ -354,6 +356,7 @@ func (r *NodeRoleNode) BuildAWSPolicy(b *PolicyBuilder) (*Policy, error) { addNodeEC2Policies(p, resource) addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.enableLifecycleHookPermissions) + addKMSGenerateRandomPolicies(p) var err error if p, err = b.AddS3Permissions(p); err != nil { @@ -866,6 +869,17 @@ func addKMSIAMPolicies(p *Policy, resource stringorslice.StringOrSlice) { }) } +func addKMSGenerateRandomPolicies(p *Policy) { + // For nodeup to seed the instance's random number generator. + p.Statement = append(p.Statement, &Statement{ + Effect: StatementEffectAllow, + Action: stringorslice.Of( + "kms:GenerateRandom", + ), + Resource: stringorslice.Slice([]string{"*"}), + }) +} + func addNodeEC2Policies(p *Policy, resource stringorslice.StringOrSlice) { // Protokube makes a DescribeInstances call, DescribeRegions when finding S3 State Bucket p.Statement = append(p.Statement, &Statement{ diff --git a/upup/pkg/fi/cloudup/apply_cluster.go b/upup/pkg/fi/cloudup/apply_cluster.go index aea69c6974..1ef8a2c586 100644 --- a/upup/pkg/fi/cloudup/apply_cluster.go +++ b/upup/pkg/fi/cloudup/apply_cluster.go @@ -1319,6 +1319,7 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAddit } config.ClusterName = cluster.ObjectMeta.Name config.InstanceGroupName = ig.ObjectMeta.Name + config.CloudProvider = cluster.Spec.CloudProvider if isMaster || useGossip { for _, arch := range architectures.GetSupported() { @@ -1349,9 +1350,8 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAddit } configServer := &nodeup.ConfigServerOptions{ - Server: baseURL.String(), - CloudProvider: cluster.Spec.CloudProvider, - CA: ca, + Server: baseURL.String(), + CA: ca, } config.ConfigServer = configServer diff --git a/upup/pkg/fi/nodeup/BUILD.bazel b/upup/pkg/fi/nodeup/BUILD.bazel index 61dbe06c15..5a2597323e 100644 --- a/upup/pkg/fi/nodeup/BUILD.bazel +++ b/upup/pkg/fi/nodeup/BUILD.bazel @@ -30,6 +30,7 @@ go_library( "//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/autoscaling:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", + "//vendor/github.com/aws/aws-sdk-go/service/kms:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library", ], ) diff --git a/upup/pkg/fi/nodeup/command.go b/upup/pkg/fi/nodeup/command.go index 7f11221427..c63c564e78 100644 --- a/upup/pkg/fi/nodeup/command.go +++ b/upup/pkg/fi/nodeup/command.go @@ -26,11 +26,13 @@ import ( "io/ioutil" "net" "net/url" + "os" "os/exec" "strconv" "strings" "time" + "github.com/aws/aws-sdk-go/service/kms" "k8s.io/kops/nodeup/pkg/model" "k8s.io/kops/nodeup/pkg/model/networking" api "k8s.io/kops/pkg/apis/kops" @@ -91,13 +93,21 @@ func (c *NodeUpCommand) Run(out io.Writer) error { return fmt.Errorf("CacheDir is required") } + region, err := getRegion(ctx, c.config) + if err != nil { + return err + } + if err = seedRNG(ctx, c.config, region); err != nil { + return err + } + var configBase vfs.Path // If we're using a config server instead of vfs, nodeConfig will hold our configuration var nodeConfig *nodeup.NodeConfig if c.config.ConfigServer != nil { - response, err := getNodeConfigFromServer(ctx, c.config.ConfigServer) + response, err := getNodeConfigFromServer(ctx, c.config, region) if err != nil { return err } @@ -182,7 +192,7 @@ func (c *NodeUpCommand) Run(out io.Writer) error { return fmt.Errorf("auxiliary config hash mismatch") } - err := evaluateSpec(c) + err = evaluateSpec(c) if err != nil { return err } @@ -209,10 +219,6 @@ func (c *NodeUpCommand) Run(out io.Writer) error { var cloud fi.Cloud if api.CloudProviderID(c.cluster.Spec.CloudProvider) == api.CloudProviderAWS { - region, err := awsup.FindRegion(c.cluster) - if err != nil { - return err - } awsCloud, err := awsup.NewAWSCloud(region, nil) if err != nil { return err @@ -674,16 +680,60 @@ func loadKernelModules(context *model.NodeupModelContext) error { return nil } -// getNodeConfigFromServer queries kops-controller for our node's configuration. -func getNodeConfigFromServer(ctx context.Context, config *nodeup.ConfigServerOptions) (*nodeup.BootstrapResponse, error) { - var authenticator fi.Authenticator - +// getRegionAndSeedRNG queries the cloud provider for the region and adds entropy to the random number generator. +func getRegion(ctx context.Context, config *nodeup.Config) (string, error) { switch api.CloudProviderID(config.CloudProvider) { case api.CloudProviderAWS: region, err := awsup.RegionFromMetadata(ctx) if err != nil { - return nil, err + return "", err } + + return region, nil + } + + return "", nil +} + +// seedRNG adds entropy to the random number generator. +func seedRNG(ctx context.Context, config *nodeup.Config, region string) error { + switch api.CloudProviderID(config.CloudProvider) { + case api.CloudProviderAWS: + config := aws.NewConfig().WithCredentialsChainVerboseErrors(true).WithRegion(region) + sess, err := session.NewSession(config) + if err != nil { + return err + } + + random, err := kms.New(sess, config).GenerateRandom(&kms.GenerateRandomInput{ + NumberOfBytes: aws.Int64(64), + }) + if err != nil { + return fmt.Errorf("generating random seed: %v", err) + } + + f, err := os.OpenFile("/dev/urandom", os.O_WRONLY, 0) + if err != nil { + return fmt.Errorf("opening /dev/urandom: %v", err) + } + _, err = f.Write(random.Plaintext) + if err1 := f.Close(); err1 != nil && err == nil { + err = err1 + } + if err != nil { + return fmt.Errorf("writing /dev/urandom: %v", err) + } + } + + return nil +} + +// getNodeConfigFromServer queries kops-controller for our node's configuration. +func getNodeConfigFromServer(ctx context.Context, config *nodeup.Config, region string) (*nodeup.BootstrapResponse, error) { + var authenticator fi.Authenticator + + switch api.CloudProviderID(config.CloudProvider) { + case api.CloudProviderAWS: a, err := awsup.NewAWSAuthenticator(region) if err != nil { return nil, err @@ -697,13 +747,13 @@ func getNodeConfigFromServer(ctx context.Context, config *nodeup.ConfigServerOpt Authenticator: authenticator, } - if config.CA != "" { - client.CA = []byte(config.CA) + if config.ConfigServer.CA != "" { + client.CA = []byte(config.ConfigServer.CA) } - u, err := url.Parse(config.Server) + u, err := url.Parse(config.ConfigServer.Server) if err != nil { - return nil, fmt.Errorf("unable to parse configuration server url %q: %w", config.Server, err) + return nil, fmt.Errorf("unable to parse configuration server url %q: %w", config.ConfigServer.Server, err) } client.BaseURL = *u