Support public buckets for serviceAccountIssuers on S3

S3 is also nudging towards bucket level permissions, so don't set an ACL when bucket is public.
This commit is contained in:
justinsb 2022-12-11 21:37:15 -05:00 committed by John Gardiner Myers
parent 994f7a17b4
commit f4984dafab
2 changed files with 93 additions and 4 deletions

View File

@ -18,6 +18,7 @@ package model
import ( import (
"bytes" "bytes"
"context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
@ -27,9 +28,11 @@ import (
"sort" "sort"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"k8s.io/klog/v2"
"k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/fitasks" "k8s.io/kops/upup/pkg/fi/fitasks"
"k8s.io/kops/util/pkg/vfs"
) )
// IssuerDiscoveryModelBuilder publish OIDC issuer discovery metadata // IssuerDiscoveryModelBuilder publish OIDC issuer discovery metadata
@ -51,6 +54,8 @@ type oidcDiscovery struct {
} }
func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error { func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error {
ctx := context.TODO()
said := b.Cluster.Spec.ServiceAccountIssuerDiscovery said := b.Cluster.Spec.ServiceAccountIssuerDiscovery
if said == nil || said.DiscoveryStore == "" { if said == nil || said.DiscoveryStore == "" {
return nil return nil
@ -71,13 +76,41 @@ func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error {
if err != nil { if err != nil {
return err return err
} }
publicFileACL := fi.PtrTo(true)
discoveryStorePath := b.Cluster.Spec.ServiceAccountIssuerDiscovery.DiscoveryStore
discoveryStore, err := vfs.Context.BuildVfsPath(discoveryStorePath)
if err != nil {
return fmt.Errorf("building VFS path for %q: %w", discoveryStorePath, err)
}
switch discoveryStore := discoveryStore.(type) {
case *vfs.S3Path:
isPublic, err := discoveryStore.IsBucketPublic(ctx)
if err != nil {
return fmt.Errorf("checking if bucket was public: %w", err)
}
if !isPublic {
klog.Infof("serviceAccountIssuers bucket %q is not public, will use object ACL", discoveryStore.Bucket())
} else {
publicFileACL = nil
}
case *vfs.MemFSPath:
// ok
default:
return fmt.Errorf("unhandled type %T", discoveryStore)
}
keysFile := &fitasks.ManagedFile{ keysFile := &fitasks.ManagedFile{
Contents: keys, Contents: keys,
Lifecycle: b.Lifecycle, Lifecycle: b.Lifecycle,
Location: fi.PtrTo("openid/v1/jwks"), Location: fi.PtrTo("openid/v1/jwks"),
Name: fi.PtrTo("keys.json"), Name: fi.PtrTo("keys.json"),
Base: fi.PtrTo(b.Cluster.Spec.ServiceAccountIssuerDiscovery.DiscoveryStore), Base: fi.PtrTo(discoveryStorePath),
PublicACL: fi.PtrTo(true), PublicACL: publicFileACL,
} }
c.AddTask(keysFile) c.AddTask(keysFile)
@ -86,8 +119,8 @@ func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error {
Lifecycle: b.Lifecycle, Lifecycle: b.Lifecycle,
Location: fi.PtrTo(".well-known/openid-configuration"), Location: fi.PtrTo(".well-known/openid-configuration"),
Name: fi.PtrTo("discovery.json"), Name: fi.PtrTo("discovery.json"),
Base: fi.PtrTo(b.Cluster.Spec.ServiceAccountIssuerDiscovery.DiscoveryStore), Base: fi.PtrTo(discoveryStorePath),
PublicACL: fi.PtrTo(true), PublicACL: publicFileACL,
} }
c.AddTask(discoveryFile) c.AddTask(discoveryFile)

View File

@ -18,6 +18,7 @@ package vfs
import ( import (
"bytes" "bytes"
"context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
@ -503,6 +504,61 @@ func (p *S3Path) GetHTTPsUrl(dualstack bool) (string, error) {
return strings.TrimSuffix(url, "/"), nil return strings.TrimSuffix(url, "/"), nil
} }
func (p *S3Path) IsBucketPublic(ctx context.Context) (bool, error) {
client, err := p.client()
if err != nil {
return false, err
}
result, err := client.GetBucketPolicyStatusWithContext(ctx, &s3.GetBucketPolicyStatusInput{})
if err != nil {
return false, fmt.Errorf("from AWS S3 GetBucketPolicyStatusWithContext: %w", err)
}
if aws.BoolValue(result.PolicyStatus.IsPublic) {
return true, nil
}
return false, nil
// We could check bucket ACLs also...
// acl, err := client.GetBucketAclWithContext(ctx, &s3.GetBucketAclInput{
// Bucket: &p.bucket,
// })
// if err != nil {
// return false, fmt.Errorf("failed to get ACL for bucket %q: %w", p.bucket, err)
// }
// allowsAnonymousRead := false
// for _, grant := range acl.Grants {
// isAllUsers := false
// switch aws.StringValue(grant.Grantee.URI) {
// case "http://acs.amazonaws.com/groups/global/AllUsers":
// isAllUsers = true
// }
// if isAllUsers {
// permission := aws.StringValue(grant.Permission)
// switch permission {
// case "FULL_CONTROL":
// klog.Warningf("bucket %q allows anonymous users full access", p.bucket)
// allowsAnonymousRead = true
// case "WRITE", "WRITE_ACP":
// klog.Warningf("bucket %q allows anonymous users write access", p.bucket)
// // it's not _read_ access
// case "READ":
// allowsAnonymousRead = true
// case "READ_ACP":
// // does not grant read
// default:
// klog.Warningf("bucket %q has unknown permission %q for anonymous access", p.bucket, permission)
// }
// }
// }
// return allowsAnonymousRead, nil
}
func (p *S3Path) IsPublic() (bool, error) { func (p *S3Path) IsPublic() (bool, error) {
client, err := p.client() client, err := p.client()
if err != nil { if err != nil {