Merge pull request #16050 from sl1pm4t/gcp-sa-issuer

gce: Add support for publishing Service Account Issuer documents to GCS
This commit is contained in:
Kubernetes Prow Robot 2023-12-03 01:41:43 +01:00 committed by GitHub
commit a4bd641630
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 1 deletions

View File

@ -1485,7 +1485,7 @@ spec:
``` ```
The `discoveryStore` option causes kOps to publish an OIDC-compatible discovery document The `discoveryStore` option causes kOps to publish an OIDC-compatible discovery document
to a path in an S3 bucket. This would ordinarily be a different bucket than the state store. to a path in an object storage bucket (such as S3 or GCS). This would ordinarily be a different bucket than the state store.
kOps will automatically configure `spec.kubeAPIServer.serviceAccountIssuer` and default kOps will automatically configure `spec.kubeAPIServer.serviceAccountIssuer` and default
`spec.kubeAPIServer.serviceAccountJWKSURI` to the corresponding `spec.kubeAPIServer.serviceAccountJWKSURI` to the corresponding
HTTPS URL. HTTPS URL.

View File

@ -220,6 +220,8 @@ func validateServiceAccountIssuerDiscovery(c *kops.Cluster, said *kops.ServiceAc
if strings.Contains(base.Bucket(), ".") { if strings.Contains(base.Bucket(), ".") {
allErrs = append(allErrs, field.Invalid(saidStoreField, saidStore, "Bucket name cannot contain dots")) allErrs = append(allErrs, field.Invalid(saidStoreField, saidStore, "Bucket name cannot contain dots"))
} }
case *vfs.GSPath:
// No known restrictions currently. Added here to avoid falling into the default catch all below.
case *vfs.MemFSPath: case *vfs.MemFSPath:
// memfs is ok for tests; not OK otherwise // memfs is ok for tests; not OK otherwise
if !base.IsClusterReadable() { if !base.IsClusterReadable() {

View File

@ -61,6 +61,11 @@ func (b *DiscoveryOptionsBuilder) BuildOptions(o interface{}) error {
if err != nil { if err != nil {
return err return err
} }
case *vfs.GSPath:
serviceAccountIssuer, err = base.GetHTTPsUrl()
if err != nil {
return err
}
case *vfs.MemFSPath: case *vfs.MemFSPath:
if !base.IsClusterReadable() { if !base.IsClusterReadable() {
// If this _is_ a test, we should call MarkClusterReadable // If this _is_ a test, we should call MarkClusterReadable

View File

@ -104,6 +104,24 @@ func (b *IssuerDiscoveryModelBuilder) Build(c *fi.CloudupModelBuilderContext) er
} else { } else {
klog.Infof("using user managed serviceAccountIssuers") klog.Infof("using user managed serviceAccountIssuers")
} }
case *vfs.GSPath:
discoveryStoreURL, err := discoveryStore.GetHTTPsUrl()
if err != nil {
return err
}
if discoveryStoreURL == fi.ValueOf(b.Cluster.Spec.KubeAPIServer.ServiceAccountIssuer) {
// Using Google Cloud Storage requires public access
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())
publicFileACL = fi.PtrTo(true)
}
} else {
klog.Infof("using user managed serviceAccountIssuers")
}
case *vfs.MemFSPath: case *vfs.MemFSPath:
// ok // ok

View File

@ -417,6 +417,46 @@ func (p *GSPath) Hash(a hashing.HashAlgorithm) (*hashing.Hash, error) {
return &hashing.Hash{Algorithm: hashing.HashAlgorithmMD5, HashValue: md5Bytes}, nil return &hashing.Hash{Algorithm: hashing.HashAlgorithmMD5, HashValue: md5Bytes}, nil
} }
func (p *GSPath) GetHTTPsUrl() (string, error) {
url := fmt.Sprintf("https://storage.googleapis.com/%s/%s", p.bucket, p.key)
return strings.TrimSuffix(url, "/"), nil
}
func (p *GSPath) IsBucketPublic(ctx context.Context) (bool, error) {
client, err := p.Client(ctx)
if err != nil {
return false, err
}
bucket, err := client.Buckets.Get(p.bucket).Do()
if err != nil {
return false, err
}
// Check bucket has uniform bucket-level IAM
if !bucket.IamConfiguration.BucketPolicyOnly.Enabled {
return false, nil
}
// Check `allUsers` IAM has `roles/storage.objectViewer` permission
policy, err := client.Buckets.GetIamPolicy(p.bucket).Do()
if err != nil {
return false, err
}
for _, binding := range policy.Bindings {
if binding.Role == "roles/storage.objectViewer" {
for _, member := range binding.Members {
if member == "allUsers" {
return true, nil
}
}
}
}
return false, nil
}
type terraformGSObject struct { type terraformGSObject struct {
Bucket string `json:"bucket" cty:"bucket"` Bucket string `json:"bucket" cty:"bucket"`
Name string `json:"name" cty:"name"` Name string `json:"name" cty:"name"`