Seed the random number generator on AWS

This commit is contained in:
John Gardiner Myers 2021-06-15 23:27:55 -07:00
parent 7ec956dd00
commit 42bf3ee85b
6 changed files with 86 additions and 26 deletions

View File

@ -43,11 +43,7 @@ func (b BootstrapClientBuilder) Build(c *fi.ModelBuilderContext) error {
var err error var err error
switch kops.CloudProviderID(b.Cluster.Spec.CloudProvider) { switch kops.CloudProviderID(b.Cluster.Spec.CloudProvider) {
case kops.CloudProviderAWS: case kops.CloudProviderAWS:
region, regionErr := awsup.FindRegion(b.Cluster) authenticator, err = awsup.NewAWSAuthenticator(b.Cloud.Region())
if regionErr != nil {
return fmt.Errorf("querying AWS region: %v", regionErr)
}
authenticator, err = awsup.NewAWSAuthenticator(region)
default: default:
return fmt.Errorf("unsupported cloud provider %s", b.Cluster.Spec.CloudProvider) return fmt.Errorf("unsupported cloud provider %s", b.Cluster.Spec.CloudProvider)
} }

View File

@ -43,6 +43,8 @@ type Config struct {
InstanceGroupRole kops.InstanceGroupRole InstanceGroupRole kops.InstanceGroupRole
// ClusterName is the name of the cluster // ClusterName is the name of the cluster
ClusterName string `json:",omitempty"` ClusterName string `json:",omitempty"`
// CloudProvider is the cloud provider in use.
CloudProvider string
// Channels is a list of channels that we should apply // Channels is a list of channels that we should apply
Channels []string `json:"channels,omitempty"` Channels []string `json:"channels,omitempty"`
// ApiserverAdditionalIPs are additional IP address to put in the apiserver server cert. // ApiserverAdditionalIPs are additional IP address to put in the apiserver server cert.
@ -91,9 +93,6 @@ type ConfigServerOptions struct {
Server string `json:"server,omitempty"` Server string `json:"server,omitempty"`
// CA is the ca-certificate to require for the configuration server // CA is the ca-certificate to require for the configuration server
CA string `json:"ca,omitempty"` 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 // Image is a docker image we should pre-load

View File

@ -247,6 +247,7 @@ func (r *NodeRoleAPIServer) BuildAWSPolicy(b *PolicyBuilder) (*Policy, error) {
addMasterEC2Policies(p, resource, b.Cluster.GetName()) addMasterEC2Policies(p, resource, b.Cluster.GetName())
addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.warmPool) addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.warmPool)
addCertIAMPolicies(p, resource) addCertIAMPolicies(p, resource)
addKMSGenerateRandomPolicies(p)
var err error var err error
if p, err = b.AddS3Permissions(p); err != nil { 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()) addMasterASPolicies(p, resource, b.Cluster.GetName())
addMasterELBPolicies(p, resource) addMasterELBPolicies(p, resource)
addCertIAMPolicies(p, resource) addCertIAMPolicies(p, resource)
addKMSGenerateRandomPolicies(p)
var err error var err error
if p, err = b.AddS3Permissions(p); err != nil { if p, err = b.AddS3Permissions(p); err != nil {
@ -354,6 +356,7 @@ func (r *NodeRoleNode) BuildAWSPolicy(b *PolicyBuilder) (*Policy, error) {
addNodeEC2Policies(p, resource) addNodeEC2Policies(p, resource)
addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.enableLifecycleHookPermissions) addASLifecyclePolicies(p, resource, b.Cluster.GetName(), r.enableLifecycleHookPermissions)
addKMSGenerateRandomPolicies(p)
var err error var err error
if p, err = b.AddS3Permissions(p); err != nil { 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) { func addNodeEC2Policies(p *Policy, resource stringorslice.StringOrSlice) {
// Protokube makes a DescribeInstances call, DescribeRegions when finding S3 State Bucket // Protokube makes a DescribeInstances call, DescribeRegions when finding S3 State Bucket
p.Statement = append(p.Statement, &Statement{ p.Statement = append(p.Statement, &Statement{

View File

@ -1319,6 +1319,7 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAddit
} }
config.ClusterName = cluster.ObjectMeta.Name config.ClusterName = cluster.ObjectMeta.Name
config.InstanceGroupName = ig.ObjectMeta.Name config.InstanceGroupName = ig.ObjectMeta.Name
config.CloudProvider = cluster.Spec.CloudProvider
if isMaster || useGossip { if isMaster || useGossip {
for _, arch := range architectures.GetSupported() { for _, arch := range architectures.GetSupported() {
@ -1349,9 +1350,8 @@ func (n *nodeUpConfigBuilder) BuildConfig(ig *kops.InstanceGroup, apiserverAddit
} }
configServer := &nodeup.ConfigServerOptions{ configServer := &nodeup.ConfigServerOptions{
Server: baseURL.String(), Server: baseURL.String(),
CloudProvider: cluster.Spec.CloudProvider, CA: ca,
CA: ca,
} }
config.ConfigServer = configServer config.ConfigServer = configServer

View File

@ -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/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/autoscaling:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/ec2: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", "//vendor/k8s.io/klog/v2:go_default_library",
], ],
) )

View File

@ -26,11 +26,13 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/url" "net/url"
"os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/service/kms"
"k8s.io/kops/nodeup/pkg/model" "k8s.io/kops/nodeup/pkg/model"
"k8s.io/kops/nodeup/pkg/model/networking" "k8s.io/kops/nodeup/pkg/model/networking"
api "k8s.io/kops/pkg/apis/kops" 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") 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 var configBase vfs.Path
// If we're using a config server instead of vfs, nodeConfig will hold our configuration // If we're using a config server instead of vfs, nodeConfig will hold our configuration
var nodeConfig *nodeup.NodeConfig var nodeConfig *nodeup.NodeConfig
if c.config.ConfigServer != nil { if c.config.ConfigServer != nil {
response, err := getNodeConfigFromServer(ctx, c.config.ConfigServer) response, err := getNodeConfigFromServer(ctx, c.config, region)
if err != nil { if err != nil {
return err return err
} }
@ -182,7 +192,7 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
return fmt.Errorf("auxiliary config hash mismatch") return fmt.Errorf("auxiliary config hash mismatch")
} }
err := evaluateSpec(c) err = evaluateSpec(c)
if err != nil { if err != nil {
return err return err
} }
@ -209,10 +219,6 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
var cloud fi.Cloud var cloud fi.Cloud
if api.CloudProviderID(c.cluster.Spec.CloudProvider) == api.CloudProviderAWS { 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) awsCloud, err := awsup.NewAWSCloud(region, nil)
if err != nil { if err != nil {
return err return err
@ -674,16 +680,60 @@ func loadKernelModules(context *model.NodeupModelContext) error {
return nil return nil
} }
// getNodeConfigFromServer queries kops-controller for our node's configuration. // getRegionAndSeedRNG queries the cloud provider for the region and adds entropy to the random number generator.
func getNodeConfigFromServer(ctx context.Context, config *nodeup.ConfigServerOptions) (*nodeup.BootstrapResponse, error) { func getRegion(ctx context.Context, config *nodeup.Config) (string, error) {
var authenticator fi.Authenticator
switch api.CloudProviderID(config.CloudProvider) { switch api.CloudProviderID(config.CloudProvider) {
case api.CloudProviderAWS: case api.CloudProviderAWS:
region, err := awsup.RegionFromMetadata(ctx) region, err := awsup.RegionFromMetadata(ctx)
if err != nil { 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) a, err := awsup.NewAWSAuthenticator(region)
if err != nil { if err != nil {
return nil, err return nil, err
@ -697,13 +747,13 @@ func getNodeConfigFromServer(ctx context.Context, config *nodeup.ConfigServerOpt
Authenticator: authenticator, Authenticator: authenticator,
} }
if config.CA != "" { if config.ConfigServer.CA != "" {
client.CA = []byte(config.CA) client.CA = []byte(config.ConfigServer.CA)
} }
u, err := url.Parse(config.Server) u, err := url.Parse(config.ConfigServer.Server)
if err != nil { 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 client.BaseURL = *u