Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boot nodes without state store access #14501

Merged
merged 5 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions cmd/kops-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ func addNodeController(mgr manager.Manager, opt *config.Options) error {
if opt.ConfigBase == "" {
return fmt.Errorf("must specify configBase")
}
if opt.SecretStore == "" {
return fmt.Errorf("must specify secretStore")
}

nodeController, err := controllers.NewLegacyNodeReconciler(mgr, opt.ConfigBase, legacyIdentifier)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cmd/kops-controller/pkg/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
type Options struct {
Cloud string `json:"cloud,omitempty"`
ConfigBase string `json:"configBase,omitempty"`
SecretStore string `json:"secretStore,omitempty"`
Server *ServerOptions `json:"server,omitempty"`
CacheNodeidentityInfo bool `json:"cacheNodeidentityInfo,omitempty"`

Expand Down
16 changes: 16 additions & 0 deletions cmd/kops-controller/pkg/server/node_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,21 @@ func (s *Server) getNodeConfig(ctx context.Context, req *nodeup.BootstrapRequest
nodeConfig.NodeupConfig = string(b)
}

{
secretIDs := []string{
"dockerconfig",
}
nodeConfig.NodeSecrets = make(map[string][]byte)
for _, id := range secretIDs {
secret, err := s.secretStore.FindSecret(id)
if err != nil {
return nil, fmt.Errorf("error loading secret %q: %w", id, err)
}
if secret != nil && secret.Data != nil {
nodeConfig.NodeSecrets[id] = secret.Data
}
}
}

return nodeConfig, nil
}
22 changes: 15 additions & 7 deletions cmd/kops-controller/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,18 @@ import (
"k8s.io/kops/pkg/pki"
"k8s.io/kops/pkg/rbac"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/secrets"
"k8s.io/kops/util/pkg/vfs"
)

type Server struct {
opt *config.Options
certNames sets.String
keypairIDs map[string]string
server *http.Server
verifier bootstrap.Verifier
keystore pki.Keystore
opt *config.Options
certNames sets.String
keypairIDs map[string]string
server *http.Server
verifier bootstrap.Verifier
keystore pki.Keystore
secretStore fi.SecretStore

// configBase is the base of the configuration storage.
configBase vfs.Path
Expand All @@ -71,10 +73,16 @@ func NewServer(opt *config.Options, verifier bootstrap.Verifier) (*Server, error

configBase, err := vfs.Context.BuildVfsPath(opt.ConfigBase)
if err != nil {
return nil, fmt.Errorf("cannot parse ConfigBase %q: %v", opt.ConfigBase, err)
return nil, fmt.Errorf("cannot parse ConfigBase %q: %w", opt.ConfigBase, err)
}
s.configBase = configBase

p, err := vfs.Context.BuildVfsPath(opt.SecretStore)
if err != nil {
return nil, fmt.Errorf("cannot parse SecretStore %q: %w", opt.SecretStore, err)
}
s.secretStore = secrets.NewVFSSecretStore(nil, p)

r := http.NewServeMux()
r.Handle("/bootstrap", http.HandlerFunc(s.bootstrap))
server.Handler = recovery(r)
Expand Down
6 changes: 3 additions & 3 deletions nodeup/pkg/model/etc_hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func (b *EtcHostsBuilder) Build(c *fi.ModelBuilderContext) error {
Addresses: []string{"127.0.0.1"},
})
}
} else if b.Cluster.UsesNoneDNS() {
} else if b.BootConfig.APIServerIP != "" {
task.Records = append(task.Records, nodetasks.HostRecord{
Hostname: b.Cluster.Spec.MasterInternalName,
Addresses: []string{b.BootConfig.APIServer},
Addresses: []string{b.BootConfig.APIServerIP},
})
if b.UseKopsControllerForNodeBootstrap() {
task.Records = append(task.Records, nodetasks.HostRecord{
Hostname: "kops-controller.internal." + b.Cluster.Name,
Addresses: []string{b.BootConfig.APIServer},
Addresses: []string{b.BootConfig.APIServerIP},
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions nodeup/pkg/model/kops_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ func (b *KopsControllerBuilder) Build(c *fi.ModelBuilderContext) error {
Subject: nodetasks.PKIXName{CommonName: "kops-controller"},
AlternateNames: []string{"kops-controller.internal." + b.Cluster.ObjectMeta.Name},
}
if b.BootConfig.APIServerIP != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also use a custom dialer that knows how to resolve the name. I thought we had that, but it might have been on a branch..

issueCert.AlternateNames = append(issueCert.AlternateNames, b.BootConfig.APIServerIP)
}
c.AddTask(issueCert)

certResource, keyResource, _ := issueCert.GetResources()
Expand Down
5 changes: 4 additions & 1 deletion pkg/apis/nodeup/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type BootstrapRequest struct {
// BootstrapResponse is a response to a BootstrapRequest.
type BootstrapResponse struct {
// Certs are the issued certificates.
Certs map[string]string
Certs map[string]string `json:"Certs,omitempty"`

// NodeConfig contains the node configuration, if IncludeNodeConfig is set.
NodeConfig *NodeConfig `json:"nodeConfig,omitempty"`
Expand All @@ -48,6 +48,9 @@ type NodeConfig struct {

// NodeupConfig holds the nodeup.Config for the node's instance group.
NodeupConfig string `json:"nodeupConfig,omitempty"`

// NodeSecrets holds the secrets for the node (like `dockerconfig`).
NodeSecrets map[string][]byte `json:"nodeSecrets,omitempty"`
}

// NodeConfigCertificate holds a certificate that the node needs to boot.
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/nodeup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ type BootConfig struct {
ConfigBase *string `json:",omitempty"`
// ConfigServer holds the configuration for the configuration server.
ConfigServer *ConfigServerOptions `json:",omitempty"`
// APIServer is the API server IP address.
// APIServerIP is the API server IP address.
// This field is used for adding an alias for api.internal. in /etc/hosts, when Topology.DNS.Type == DNSTypeNone.
APIServer string `json:",omitempty"`
APIServerIP string `json:",omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This one I think is OK from a skew point of view, but maybe we should make this hosts map[string]string or something?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a map of well-known hosts, like Hosts map[string]string

Copy link
Member Author

@hakman hakman Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a good idea, but it's a larger change that should be done separately.

// InstanceGroupName is the name of the instance group.
InstanceGroupName string `json:",omitempty"`
// InstanceGroupRole is the instance group role.
Expand Down
25 changes: 19 additions & 6 deletions pkg/configserver/secretstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,31 @@ package configserver
import (
"fmt"

"k8s.io/kops/pkg/apis/nodeup"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/util/pkg/vfs"
)

// configserverSecretStore is a SecretStore backed by the config server.
type configserverSecretStore struct {
nodeConfig *nodeup.NodeConfig
nodeSecrets map[string][]byte
}

func NewSecretStore(nodeConfig *nodeup.NodeConfig) fi.SecretStore {
func NewSecretStore(nodeSecrets map[string][]byte) fi.SecretStore {
return &configserverSecretStore{
nodeConfig: nodeConfig,
nodeSecrets: nodeSecrets,
}
}

// Secret implements fi.SecretStore
func (s *configserverSecretStore) Secret(id string) (*fi.Secret, error) {
return nil, fmt.Errorf("Secret not supported by configserverSecretStore")
secret, err := s.FindSecret(id)
if err != nil {
return nil, err
}
if secret == nil {
return nil, fmt.Errorf("secret %q not found", id)
}
return secret, nil
}

// DeleteSecret implements fi.SecretStore
Expand All @@ -47,7 +53,14 @@ func (s *configserverSecretStore) DeleteSecret(id string) error {

// FindSecret implements fi.SecretStore
func (s *configserverSecretStore) FindSecret(id string) (*fi.Secret, error) {
return nil, fmt.Errorf("FindSecret not supported by configserverSecretStore")
secretBytes, ok := s.nodeSecrets[id]
if !ok {
return nil, nil
}
secret := &fi.Secret{
Data: secretBytes,
}
return secret, nil
}

// GetOrCreateSecret implements fi.SecretStore
Expand Down
2 changes: 0 additions & 2 deletions pkg/featureflag/featureflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ var (
ClusterAddons = new("ClusterAddons", Bool(false))
// Azure toggles the Azure support.
Azure = new("Azure", Bool(false))
// KopsControllerStateStore enables fetching the kops state from kops-controller, instead of requiring access to S3/GCS/etc.
KopsControllerStateStore = new("KopsControllerStateStore", Bool(false))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just switch the default to true, first? That gives us an "out" until we have more test coverage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll get a fair amount of test coverage before we release. The only area of concern would be the lack of coverage for dockerconfig. But I think leaving in the old code would be riskier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is more clear later. I am using UseKopsControllerForNodeBootstrap(cluster) instead of the flag.

// APIServerNodes enables ability to provision nodes that only run the kube-apiserver.
APIServerNodes = new("APIServerNodes", Bool(false))
// UseAddonOperators activates experimental addon operator support
Expand Down
2 changes: 1 addition & 1 deletion pkg/model/bootstrapscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (b *BootstrapScript) buildEnvironmentVariables(cluster *kops.Cluster) (map[
env["GOSSIP_DNS_CONN_LIMIT"] = os.Getenv("GOSSIP_DNS_CONN_LIMIT")
}

if os.Getenv("S3_ENDPOINT") != "" {
if os.Getenv("S3_ENDPOINT") != "" && (!model.UseKopsControllerForNodeBootstrap(cluster) || b.ig.IsMaster()) {
johngmyers marked this conversation as resolved.
Show resolved Hide resolved
env["S3_ENDPOINT"] = os.Getenv("S3_ENDPOINT")
env["S3_REGION"] = os.Getenv("S3_REGION")
env["S3_ACCESS_KEY_ID"] = os.Getenv("S3_ACCESS_KEY_ID")
Expand Down
14 changes: 6 additions & 8 deletions pkg/model/iam/iam_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,16 +700,14 @@ func ReadableStatePaths(cluster *kops.Cluster, role Subject) ([]string, error) {
paths = append(paths, "/*")

case *NodeRoleNode:
paths = append(paths,
"/addons/*",
"/cluster-completed.spec",
"/igconfig/node/*",
"/secrets/dockerconfig",
)

// Give access to keys for client certificates as needed.
if !model.UseKopsControllerForNodeBootstrap(cluster) {
paths = append(paths, "/pki/private/kube-proxy/*")
paths = append(paths,
"/cluster-completed.spec",
"/igconfig/node/*",
"/secrets/dockerconfig",
"/pki/private/kube-proxy/*",
)

if useBootstrapTokens(cluster) {
paths = append(paths, "/pki/private/node-authorizer-client/*")
Expand Down
12 changes: 0 additions & 12 deletions pkg/model/iam/tests/iam_builder_node_strict.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
{
"Statement": [
{
"Action": [
"s3:Get*"
],
"Effect": "Allow",
"Resource": [
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/addons/*",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/cluster-completed.spec",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/igconfig/node/*",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/secrets/dockerconfig"
]
},
{
"Action": [
"s3:GetBucketLocation",
Expand Down
12 changes: 0 additions & 12 deletions pkg/model/iam/tests/iam_builder_node_strict_ecr.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
{
"Statement": [
{
"Action": [
"s3:Get*"
],
"Effect": "Allow",
"Resource": [
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/addons/*",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/cluster-completed.spec",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/igconfig/node/*",
"arn:aws-test:s3:::kops-tests/iam-builder-test.k8s.local/secrets/dockerconfig"
]
},
{
"Action": [
"s3:GetBucketLocation",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
{
"Statement": [
{
"Action": [
"s3:Get*"
],
"Effect": "Allow",
"Resource": [
"arn:aws-test:s3:::placeholder-read-bucket/tests/additionalobjects.example.com/addons/*",
"arn:aws-test:s3:::placeholder-read-bucket/tests/additionalobjects.example.com/cluster-completed.spec",
"arn:aws-test:s3:::placeholder-read-bucket/tests/additionalobjects.example.com/igconfig/node/*",
"arn:aws-test:s3:::placeholder-read-bucket/tests/additionalobjects.example.com/secrets/dockerconfig"
]
},
{
"Action": [
"s3:GetBucketLocation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,32 @@ __EOF_CLUSTER_SPEC

cat > conf/kube_env.yaml << '__EOF_KUBE_ENV'
CloudProvider: aws
ConfigBase: memfs://tests/additionalobjects.example.com
ConfigServer:
CACertificates: |
-----BEGIN CERTIFICATE-----
MIIBbjCCARigAwIBAgIMFpANqBD8NSD82AUSMA0GCSqGSIb3DQEBCwUAMBgxFjAU
BgNVBAMTDWt1YmVybmV0ZXMtY2EwHhcNMjEwNzA3MDcwODAwWhcNMzEwNzA3MDcw
ODAwWjAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMFwwDQYJKoZIhvcNAQEBBQAD
SwAwSAJBANFI3zr0Tk8krsW8vwjfMpzJOlWQ8616vG3YPa2qAgI7V4oKwfV0yIg1
jt+H6f4P/wkPAPTPTfRp9Iy8oHEEFw0CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNG3zVjTcLlJwDsJ4/K9DV7KohUA
MA0GCSqGSIb3DQEBCwUAA0EAB8d03fY2w7WKpfO29qI295pu2C4ca9AiVGOpgSc8
tmQsq6rcxt3T+rb589PVtz0mw/cKTxOk6gH2CCC+yHfy2w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBbjCCARigAwIBAgIMFpANvmSa0OAlYmXKMA0GCSqGSIb3DQEBCwUAMBgxFjAU
BgNVBAMTDWt1YmVybmV0ZXMtY2EwHhcNMjEwNzA3MDcwOTM2WhcNMzEwNzA3MDcw
OTM2WjAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMFwwDQYJKoZIhvcNAQEBBQAD
SwAwSAJBAMF6F4aZdpe0RUpyykaBpWwZCnwbffhYGOw+fs6RdLuUq7QCNmJm/Eq7
WWOziMYDiI9SbclpD+6QiJ0N3EqppVUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLImp6ARjPDAH6nhI+scWVt3Q9bn
MA0GCSqGSIb3DQEBCwUAA0EAVQVx5MUtuAIeePuP9o51xtpT2S6Fvfi8J4ICxnlA
9B7UD2ushcVFPtaeoL9Gfu8aY4KJBeqqg5ojl4qmRnThjw==
-----END CERTIFICATE-----
server: https://kops-controller.internal.additionalobjects.example.com:3988/
InstanceGroupName: nodes
InstanceGroupRole: Node
NodeupConfigHash: C3AAcVpXOs5a3YimhkM9Tew4y0StMn7Nm3g9SjV40Ns=
NodeupConfigHash: f3R1TTQHlunby5WR535z2HNp5kgF2rT7b8Dtk/6P28U=

__EOF_KUBE_ENV

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ spec:
addons:
- id: k8s-1.16
manifest: kops-controller.addons.k8s.io/k8s-1.16.yaml
manifestHash: 22c6990a47418fe74b17cc165450e0c1b3f05de916fa86368e759a22cd694a13
manifestHash: 1ea89d0f2952221756ab806fdcab08098333fa6b8b3f3af979149db9209c56ab
name: kops-controller.addons.k8s.io
needsRollingUpdate: control-plane
selector:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v1
data:
config.yaml: |
{"cloud":"aws","configBase":"memfs://tests/additionalobjects.example.com","server":{"Listen":":3988","provider":{"aws":{"nodesRoles":["nodes.additionalobjects.example.com"],"Region":"us-test-1"}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["kubernetes-ca"],"certNames":["kubelet","kubelet-server","kube-proxy"]}}
{"cloud":"aws","configBase":"memfs://tests/additionalobjects.example.com","secretStore":"memfs://tests/additionalobjects.example.com/secrets","server":{"Listen":":3988","provider":{"aws":{"nodesRoles":["nodes.additionalobjects.example.com"],"Region":"us-test-1"}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["kubernetes-ca"],"certNames":["kubelet","kubelet-server","kube-proxy"]}}
kind: ConfigMap
metadata:
creationTimestamp: null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,7 @@ Assets:
- ef17764ffd6cdcb16d76401bac1db6acc050c9b088f1be5efa0e094ea3b01df0@https://storage.googleapis.com/k8s-artifacts-cni/release/v0.9.1/cni-plugins-linux-arm64-v0.9.1.tgz
- 6d655e80a843f480e1c1cead18479185251581ff2d4a2e2e5eb88ad5b5e3d937@https://github.com/containerd/containerd/releases/download/v1.6.10/containerd-1.6.10-linux-arm64.tar.gz
- dbb71e737eaef454a406ce21fd021bd8f1b35afb7635016745992bbd7c17a223@https://github.com/opencontainers/runc/releases/download/v1.1.4/runc.arm64
CAs:
kubernetes-ca: |
-----BEGIN CERTIFICATE-----
MIIBbjCCARigAwIBAgIMFpANqBD8NSD82AUSMA0GCSqGSIb3DQEBCwUAMBgxFjAU
BgNVBAMTDWt1YmVybmV0ZXMtY2EwHhcNMjEwNzA3MDcwODAwWhcNMzEwNzA3MDcw
ODAwWjAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMFwwDQYJKoZIhvcNAQEBBQAD
SwAwSAJBANFI3zr0Tk8krsW8vwjfMpzJOlWQ8616vG3YPa2qAgI7V4oKwfV0yIg1
jt+H6f4P/wkPAPTPTfRp9Iy8oHEEFw0CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNG3zVjTcLlJwDsJ4/K9DV7KohUA
MA0GCSqGSIb3DQEBCwUAA0EAB8d03fY2w7WKpfO29qI295pu2C4ca9AiVGOpgSc8
tmQsq6rcxt3T+rb589PVtz0mw/cKTxOk6gH2CCC+yHfy2w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBbjCCARigAwIBAgIMFpANvmSa0OAlYmXKMA0GCSqGSIb3DQEBCwUAMBgxFjAU
BgNVBAMTDWt1YmVybmV0ZXMtY2EwHhcNMjEwNzA3MDcwOTM2WhcNMzEwNzA3MDcw
OTM2WjAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMFwwDQYJKoZIhvcNAQEBBQAD
SwAwSAJBAMF6F4aZdpe0RUpyykaBpWwZCnwbffhYGOw+fs6RdLuUq7QCNmJm/Eq7
WWOziMYDiI9SbclpD+6QiJ0N3EqppVUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLImp6ARjPDAH6nhI+scWVt3Q9bn
MA0GCSqGSIb3DQEBCwUAA0EAVQVx5MUtuAIeePuP9o51xtpT2S6Fvfi8J4ICxnlA
9B7UD2ushcVFPtaeoL9Gfu8aY4KJBeqqg5ojl4qmRnThjw==
-----END CERTIFICATE-----
CAs: {}
ClusterName: additionalobjects.example.com
Hooks:
- null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1521,18 +1521,6 @@
],
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:Get*"
],
"Effect": "Allow",
"Resource": [
"arn:aws-test:s3:::placeholder-read-bucket/clusters.example.com/minimal.example.com/addons/*",
"arn:aws-test:s3:::placeholder-read-bucket/clusters.example.com/minimal.example.com/cluster-completed.spec",
"arn:aws-test:s3:::placeholder-read-bucket/clusters.example.com/minimal.example.com/igconfig/node/*",
"arn:aws-test:s3:::placeholder-read-bucket/clusters.example.com/minimal.example.com/secrets/dockerconfig"
]
},
{
"Action": [
"s3:GetBucketLocation",
Expand Down
Loading