Skip to content

Commit

Permalink
terraform target now checks publicIP for allocation ID
Browse files Browse the repository at this point in the history
Generate new clientset after bastion model updated

enable FS kops state storage

iam/s3 permissions

Grab second to last token

glog to klog

IAM S3 Path

Generated clientset: make apimachinery

One more fix for iamS3Path

Fix bucket issue
  • Loading branch information
Andrew Ballman committed Oct 9, 2020
1 parent 453d7d9 commit d7a10b7
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 116 deletions.
247 changes: 139 additions & 108 deletions pkg/model/iam/iam_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,119 @@ func (b *PolicyBuilder) IAMPrefix() string {
}
}

func (b *PolicyBuilder) buildAWSS3Policy(p *Policy, bucket string, key string) *Policy {
iamS3Path := bucket + "/" + key
iamS3Path = strings.TrimSuffix(iamS3Path, "/")

p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Of(
"s3:GetBucketLocation",
"s3:GetEncryptionConfiguration",
"s3:ListBucket",
"s3:ListBucketVersions",
),
Resource: stringorslice.Slice([]string{
strings.Join([]string{b.IAMPrefix(), ":s3:::", bucket}, ""),
}),
})

if b.Cluster.Spec.IAM.Legacy {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
} else {
if b.Role == kops.InstanceGroupRoleMaster {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
} else if b.Role == kops.InstanceGroupRoleNode {
resources := []string{
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/addons/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/cluster.spec"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/config"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/instancegroup/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/issued/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kube-proxy/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/ssh/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/secrets/dockerconfig"}, ""),
}

// @check if bootstrap tokens are enabled and if so enable access to client certificate
if b.UseBootstrapTokens() {
resources = append(resources, strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/node-authorizer-client/*"}, ""))
} else {
resources = append(resources, strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kubelet/*"}, ""))
}

sort.Strings(resources)

p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(resources...),
})

networkingSpec := b.Cluster.Spec.Networking

if networkingSpec != nil {
// @check if kuberoute is enabled and permit access to the private key
if networkingSpec.Kuberouter != nil {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kube-router/*"}, ""),
),
})
}

// @check if calico is enabled as the CNI provider and permit access to the client TLS certificate by default
if networkingSpec.Calico != nil {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/calico-client/*"}, ""),
),
})
}

// @check if cilium is enabled as the CNI provider and permit access to the cilium etc client TLS certificate by default
// As long as the Cilium Etcd cluster exists, we should do this
ciliumEtcd := false

for _, cluster := range b.Cluster.Spec.EtcdClusters {
if cluster.Name == "cilium" {
ciliumEtcd = true
break
}
}

if networkingSpec.Cilium != nil && ciliumEtcd {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/etcd-clients-ca-cilium/*"}, ""),
),
})
}
}
}
}

return p
}

// AddS3Permissions updates an IAM Policy with statements granting tailored
// access to S3 assets, depending on the instance group role
func (b *PolicyBuilder) AddS3Permissions(p *Policy) (*Policy, error) {
Expand Down Expand Up @@ -323,117 +436,19 @@ func (b *PolicyBuilder) AddS3Permissions(p *Policy) (*Policy, error) {
}

if s3Path, ok := vfsPath.(*vfs.S3Path); ok {
iamS3Path := s3Path.Bucket() + "/" + s3Path.Key()
iamS3Path = strings.TrimSuffix(iamS3Path, "/")

p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Of(
"s3:GetBucketLocation",
"s3:GetEncryptionConfiguration",
"s3:ListBucket",
"s3:ListBucketVersions",
),
Resource: stringorslice.Slice([]string{
strings.Join([]string{b.IAMPrefix(), ":s3:::", s3Path.Bucket()}, ""),
}),
})

if b.Cluster.Spec.IAM.Legacy {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
} else {
if b.Role == kops.InstanceGroupRoleMaster {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
} else if b.Role == kops.InstanceGroupRoleNode {
resources := []string{
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/addons/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/cluster.spec"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/config"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/instancegroup/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/issued/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kube-proxy/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/ssh/*"}, ""),
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/secrets/dockerconfig"}, ""),
}

// @check if bootstrap tokens are enabled and if so enable access to client certificate
if b.UseBootstrapTokens() {
resources = append(resources, strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/node-authorizer-client/*"}, ""))
} else {
resources = append(resources, strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kubelet/*"}, ""))
}

sort.Strings(resources)

p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(resources...),
})

networkingSpec := b.Cluster.Spec.Networking

if networkingSpec != nil {
// @check if kuberoute is enabled and permit access to the private key
if networkingSpec.Kuberouter != nil {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/kube-router/*"}, ""),
),
})
}

// @check if calico is enabled as the CNI provider and permit access to the client TLS certificate by default
if networkingSpec.Calico != nil {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/calico-client/*"}, ""),
),
})
}

// @check if cilium is enabled as the CNI provider and permit access to the cilium etc client TLS certificate by default
// As long as the Cilium Etcd cluster exists, we should do this
ciliumEtcd := false

for _, cluster := range b.Cluster.Spec.EtcdClusters {
if cluster.Name == "cilium" {
ciliumEtcd = true
break
}
}

if networkingSpec.Cilium != nil && ciliumEtcd {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/pki/private/etcd-clients-ca-cilium/*"}, ""),
),
})
}
}
}
}
p = b.buildAWSS3Policy(p, s3Path.Bucket(), s3Path.Key())
} else if _, ok := vfsPath.(*vfs.MemFSPath); ok {
// Tests -ignore - nothing we can do in terms of IAM policy
klog.Warningf("ignoring memfs path %q for IAM policy builder", vfsPath)
} else if vfs, ok := vfsPath.(*vfs.FSPath); ok {
if b.Cluster.Spec.CloudProvider == string(kops.CloudProviderAWS) {
splitPath := strings.Split(strings.TrimSuffix(vfs.Path(), "/"), "/")
bucket := splitPath[len(splitPath)-2]

p = b.buildAWSS3Policy(p, bucket, vfs.Base())
} else {
return nil, fmt.Errorf("path is not cluster readable: %v", root)
}
} else {
// We could implement this approach, but it seems better to
// get all clouds using cluster-readable storage
Expand Down Expand Up @@ -463,6 +478,22 @@ func (b *PolicyBuilder) AddS3Permissions(p *Policy) (*Policy, error) {
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
} else if vfs, ok := vfsPath.(*vfs.FSPath); ok {
if b.Cluster.Spec.CloudProvider == string(kops.CloudProviderAWS) {
// Tests -ignore - nothing we can do in terms of IAM policy
splitPath := strings.Split(strings.TrimSuffix(vfs.Path(), "/"), "/")

bucket := strings.TrimSuffix(splitPath[len(splitPath)-2], "/")
iamS3Path := strings.Join([]string{bucket, vfs.Base()}, "/")

p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:GetObject", "s3:DeleteObject", "s3:PutObject"}),
Resource: stringorslice.Of(
strings.Join([]string{b.IAMPrefix(), ":s3:::", iamS3Path, "/*"}, ""),
),
})
}
} else {
klog.Warningf("unknown writeable path, can't apply IAM policy: %q", vfsPath)
}
Expand Down
51 changes: 43 additions & 8 deletions upup/pkg/fi/cloudup/awstasks/elastic_ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ type ElasticIP struct {
Name *string
Lifecycle *fi.Lifecycle

ID *string
PublicIP *string
ID *string
PublicIP *string
AllocationID *string

// ElasticIPs don't support tags. We instead find it via a related resource.

Expand Down Expand Up @@ -284,17 +285,51 @@ type terraformElasticIP struct {
Tags map[string]string `json:"tags,omitempty" cty:"tags"`
}

func (_ *ElasticIP) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *ElasticIP) error {
tf := &terraformElasticIP{
VPC: aws.Bool(true),
Tags: e.Tags,
func (e *ElasticIP) FindAllocationID(publicIP *string, cloud awsup.AWSCloud) (*string, error) {
if publicIP == nil {
return nil, fmt.Errorf("cannot find allocation ID without ElasticIP publicIP")
}

return t.RenderResource("aws_eip", *e.Name, tf)
request := &ec2.DescribeAddressesInput{Filters: []*ec2.Filter{awsup.NewEC2Filter("public-ip", *publicIP)}}
response, err := cloud.EC2().DescribeAddresses(request)
if err != nil {
return nil, fmt.Errorf("error listing ElasticIPs: %v", err)
}

if response == nil || len(response.Addresses) == 0 {
return nil, nil
}

if len(response.Addresses) != 1 {
return nil, fmt.Errorf("found multiple ElasticIPs for: %v", e)
}

klog.V(2).Infof("Found ElasticIP Allocation ID for EIP: %q - %q", *publicIP, response.Addresses[0].AllocationId)
return response.Addresses[0].AllocationId, nil
}

func (eip *ElasticIP) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *ElasticIP) error {
if e.PublicIP == nil {
tf := &terraformElasticIP{
VPC: aws.Bool(true),
Tags: e.Tags,
}

return t.RenderResource("aws_eip", *e.Name, tf)
}

e.AllocationID, _ = e.FindAllocationID(e.PublicIP, t.Cloud.(awsup.AWSCloud))
return nil
}

func (e *ElasticIP) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_eip", *e.Name, "id")
if e.PublicIP == nil {
return terraform.LiteralProperty("aws_eip", *e.Name, "id")
} else if e.AllocationID != nil {
return terraform.LiteralFromStringValue(*e.AllocationID)
} else {
return nil
}
}

type cloudformationElasticIP struct {
Expand Down

0 comments on commit d7a10b7

Please sign in to comment.