|
| 1 | +package eks |
| 2 | + |
| 3 | +import ( |
| 4 | + "crypto/tls" |
| 5 | + "crypto/x509" |
| 6 | + "fmt" |
| 7 | + "net" |
| 8 | + "net/http" |
| 9 | + "net/http/httptest" |
| 10 | + "net/url" |
| 11 | + "testing" |
| 12 | + |
| 13 | + "github.com/aws/aws-sdk-go/aws" |
| 14 | + "github.com/aws/aws-sdk-go/service/eks" |
| 15 | + "github.com/aws/aws-sdk-go/service/iam" |
| 16 | + "github.com/golang/mock/gomock" |
| 17 | + . "github.com/onsi/gomega" |
| 18 | + corev1 "k8s.io/api/core/v1" |
| 19 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 20 | + "k8s.io/apimachinery/pkg/runtime" |
| 21 | + "sigs.k8s.io/controller-runtime/pkg/client/fake" |
| 22 | + |
| 23 | + infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta2" |
| 24 | + ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta2" |
| 25 | + "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" |
| 26 | + "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/iamauth/mock_iamauth" |
| 27 | + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" |
| 28 | +) |
| 29 | + |
| 30 | +func TestOIDCReconcile(t *testing.T) { |
| 31 | + tests := []struct { |
| 32 | + name string |
| 33 | + expect func(m *mock_iamauth.MockIAMAPIMockRecorder, url string) |
| 34 | + cluster func(url string) eks.Cluster |
| 35 | + }{ |
| 36 | + { |
| 37 | + name: "cluster create with no OIDC provider present yet should create one", |
| 38 | + cluster: func(url string) eks.Cluster { |
| 39 | + return eks.Cluster{ |
| 40 | + Name: aws.String("cluster-test"), |
| 41 | + Arn: aws.String("arn:arn"), |
| 42 | + RoleArn: aws.String("arn:role"), |
| 43 | + Identity: &eks.Identity{ |
| 44 | + Oidc: &eks.OIDC{ |
| 45 | + Issuer: aws.String(url), |
| 46 | + }, |
| 47 | + }, |
| 48 | + } |
| 49 | + }, |
| 50 | + expect: func(m *mock_iamauth.MockIAMAPIMockRecorder, url string) { |
| 51 | + m.ListOpenIDConnectProviders(&iam.ListOpenIDConnectProvidersInput{}).Return(&iam.ListOpenIDConnectProvidersOutput{ |
| 52 | + OpenIDConnectProviderList: []*iam.OpenIDConnectProviderListEntry{}, |
| 53 | + }, nil) |
| 54 | + m.CreateOpenIDConnectProvider(&iam.CreateOpenIDConnectProviderInput{ |
| 55 | + ClientIDList: aws.StringSlice([]string{"sts.amazonaws.com"}), |
| 56 | + ThumbprintList: aws.StringSlice([]string{"c7a33e1de97f8bf5413ef9a833da98507c95416c"}), |
| 57 | + Url: &url, |
| 58 | + }).Return(&iam.CreateOpenIDConnectProviderOutput{ |
| 59 | + OpenIDConnectProviderArn: aws.String("arn::oidc"), |
| 60 | + }, nil) |
| 61 | + }, |
| 62 | + }, |
| 63 | + { |
| 64 | + name: "cluster create with existing OIDC provider which is retrieved", |
| 65 | + cluster: func(url string) eks.Cluster { |
| 66 | + return eks.Cluster{ |
| 67 | + Name: aws.String("cluster-test"), |
| 68 | + Arn: aws.String("arn:arn"), |
| 69 | + RoleArn: aws.String("arn:role"), |
| 70 | + Identity: &eks.Identity{ |
| 71 | + Oidc: &eks.OIDC{ |
| 72 | + Issuer: aws.String(url), |
| 73 | + }, |
| 74 | + }, |
| 75 | + } |
| 76 | + }, |
| 77 | + expect: func(m *mock_iamauth.MockIAMAPIMockRecorder, url string) { |
| 78 | + m.ListOpenIDConnectProviders(&iam.ListOpenIDConnectProvidersInput{}).Return(&iam.ListOpenIDConnectProvidersOutput{ |
| 79 | + OpenIDConnectProviderList: []*iam.OpenIDConnectProviderListEntry{ |
| 80 | + { |
| 81 | + Arn: aws.String("arn::oidc"), |
| 82 | + }, |
| 83 | + }, |
| 84 | + }, nil) |
| 85 | + // This should equal with what we provide. |
| 86 | + m.GetOpenIDConnectProvider(&iam.GetOpenIDConnectProviderInput{ |
| 87 | + OpenIDConnectProviderArn: aws.String("arn::oidc"), |
| 88 | + }).Return(&iam.GetOpenIDConnectProviderOutput{ |
| 89 | + ClientIDList: aws.StringSlice([]string{"sts.amazonaws.com"}), |
| 90 | + ThumbprintList: aws.StringSlice([]string{"c7a33e1de97f8bf5413ef9a833da98507c95416c"}), |
| 91 | + Url: &url, |
| 92 | + }, nil) |
| 93 | + }, |
| 94 | + }, |
| 95 | + } |
| 96 | + |
| 97 | + for _, tc := range tests { |
| 98 | + t.Run(tc.name, func(t *testing.T) { |
| 99 | + g := NewWithT(t) |
| 100 | + |
| 101 | + mockControl := gomock.NewController(t) |
| 102 | + defer mockControl.Finish() |
| 103 | + |
| 104 | + scheme := runtime.NewScheme() |
| 105 | + _ = infrav1.AddToScheme(scheme) |
| 106 | + _ = ekscontrolplanev1.AddToScheme(scheme) |
| 107 | + _ = corev1.AddToScheme(scheme) |
| 108 | + |
| 109 | + ts, url, err := testServer(serverCert) |
| 110 | + g.Expect(err).To(Succeed()) |
| 111 | + defer ts.Close() |
| 112 | + |
| 113 | + controlPlane := &ekscontrolplanev1.AWSManagedControlPlane{ |
| 114 | + ObjectMeta: metav1.ObjectMeta{ |
| 115 | + Name: "test-source", |
| 116 | + Namespace: "ns", |
| 117 | + }, |
| 118 | + Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{ |
| 119 | + Version: aws.String("1.25"), |
| 120 | + AssociateOIDCProvider: true, |
| 121 | + }, |
| 122 | + } |
| 123 | + secret := &corev1.Secret{ |
| 124 | + ObjectMeta: metav1.ObjectMeta{ |
| 125 | + Name: "capi-name-kubeconfig", |
| 126 | + Namespace: "ns", |
| 127 | + }, |
| 128 | + Data: map[string][]byte{ |
| 129 | + "value": kubeConfig, |
| 130 | + }, |
| 131 | + } |
| 132 | + client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(controlPlane, secret).Build() |
| 133 | + scope, _ := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{ |
| 134 | + Client: client, |
| 135 | + Cluster: &clusterv1.Cluster{ |
| 136 | + ObjectMeta: metav1.ObjectMeta{ |
| 137 | + Namespace: "ns", |
| 138 | + Name: "capi-name", |
| 139 | + }, |
| 140 | + }, |
| 141 | + ControlPlane: controlPlane, |
| 142 | + EnableIAM: true, |
| 143 | + }) |
| 144 | + |
| 145 | + iamMock := mock_iamauth.NewMockIAMAPI(mockControl) |
| 146 | + tc.expect(iamMock.EXPECT(), url.String()) |
| 147 | + s := NewService(scope) |
| 148 | + s.IAMClient = iamMock |
| 149 | + |
| 150 | + cluster := tc.cluster(url.String()) |
| 151 | + err = s.reconcileOIDCProvider(&cluster) |
| 152 | + // We reached the trusted policy reconcile which will fail because it tries to connect to the server. |
| 153 | + // But at this point, we already know that the critical area has been covered. |
| 154 | + g.Expect(err).To(MatchError(ContainSubstring("dial tcp: lookup test-cluster-api.nodomain.example.com: no such host"))) |
| 155 | + }) |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +func testServer(serverCert []byte) (*httptest.Server, *url.URL, error) { |
| 160 | + rootCAs := x509.NewCertPool() |
| 161 | + |
| 162 | + cert, err := tls.X509KeyPair(serverCert, serverKey) |
| 163 | + if err != nil { |
| 164 | + return nil, nil, fmt.Errorf("failed to init x509 cert/key pair: %w", err) |
| 165 | + } |
| 166 | + tlsConfig := &tls.Config{ |
| 167 | + Certificates: []tls.Certificate{cert}, |
| 168 | + RootCAs: rootCAs, |
| 169 | + MinVersion: tls.VersionTLS12, |
| 170 | + } |
| 171 | + |
| 172 | + tlsServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| 173 | + w.WriteHeader(http.StatusOK) |
| 174 | + w.Write([]byte("ok")) |
| 175 | + })) |
| 176 | + |
| 177 | + tlsServer.TLS = tlsConfig |
| 178 | + tlsServer.StartTLS() |
| 179 | + |
| 180 | + serverURL, err := url.Parse(tlsServer.URL) |
| 181 | + if err != nil { |
| 182 | + tlsServer.Close() |
| 183 | + return nil, nil, fmt.Errorf("failed to parse the testserver URL: %w", err) |
| 184 | + } |
| 185 | + serverURL.Host = net.JoinHostPort("localhost", serverURL.Port()) |
| 186 | + |
| 187 | + return tlsServer, serverURL, nil |
| 188 | +} |
| 189 | + |
| 190 | +// generated with `mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1`. |
| 191 | +var serverCert = []byte(`-----BEGIN CERTIFICATE----- |
| 192 | +MIIEkDCCAvigAwIBAgIQKmk0KCrPT4AkOBJ2bIML1TANBgkqhkiG9w0BAQsFADCB |
| 193 | +iTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS8wLQYDVQQLDCZza2Fy |
| 194 | +bHNvQEdCcy1NYWNCb29rLVByby5jaGVsbG8uaHUgKEdCKTE2MDQGA1UEAwwtbWtj |
| 195 | +ZXJ0IHNrYXJsc29AR0JzLU1hY0Jvb2stUHJvLmNoZWxsby5odSAoR0IpMB4XDTIy |
| 196 | +MDkyOTE5NTAzOVoXDTI0MTIyOTIwNTAzOVowWjEnMCUGA1UEChMebWtjZXJ0IGRl |
| 197 | +dmVsb3BtZW50IGNlcnRpZmljYXRlMS8wLQYDVQQLDCZza2FybHNvQEdCcy1NYWNC |
| 198 | +b29rLVByby5jaGVsbG8uaHUgKEdCKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC |
| 199 | +AQoCggEBAKo3e8iCx2fFzcohYZgi9Z7OMs31jlqG4E9lxAolQ4hi6ehPoAgz8NB1 |
| 200 | +X5nwqjN63E9L8nsGIASS6+PkQGS06HQgV9F5EoXl7fdrAwCMkOLj7fwrTUHIrYNV |
| 201 | +XCCPdeo6d9yWAREjjCOaE31taFx8/VBkH11/F7nSsNbkGyoFN9Ob0Z5dPxoDp/it |
| 202 | +YzyKpOLEXRrfKA44+gCnE2wczri1qdMVa7gI9EnwZKa6aiuOKwR20X6gdBXW7eSR |
| 203 | +UaU67NXl5bxzc1BiRdXI/k0tAt0E7k0xz1qcqVLeLFriHDt30y5yV627B1qis1KI |
| 204 | +EJVgZ2LrB/9h9JMOlBuyUcMoBCC56P0CAwEAAaOBoTCBnjAOBgNVHQ8BAf8EBAMC |
| 205 | +BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUe+QKVJzlMSlp9SBA |
| 206 | +8ojl54B4VB8wVgYDVR0RBE8wTYILZXhhbXBsZS5jb22CDSouZXhhbXBsZS5jb22C |
| 207 | +DGV4YW1wbGUudGVzdIIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAAB |
| 208 | +MA0GCSqGSIb3DQEBCwUAA4IBgQAYnY9zDQb6vQwkO7e5n4ZMuH/4GXpwO+CBEfTH |
| 209 | +sMk9fM/T88vcNo085scX+5LMUI+jLFWV0kBLwasLDMVUMQVPwDsVcTgGFc3rTfjd |
| 210 | +D3LwEaGl67+jo+a9CWfcbhC3KYJFKgofceEVI6D+lqTsmVLs6wCkiQal683KZc06 |
| 211 | +5qHXpRDwbNDVh8Yj9RSXBAQTPeI4dKReS3xMr8bSzHKd/M+UCuzT3taLSqbB5RRZ |
| 212 | +a35SfMCqz2DDfrQMb5uMTaV7MyyCoYUqJ9JDlGKq4JuayUpBpsw2fyLUcp3rfUzI |
| 213 | +I7PTHEEvVq3JOMcM1/wngAJ9sRYng92Fdo0SbN3fT+HVTwef45DKQ7dWwkbjU3EH |
| 214 | +1CYm+MCwt2kt2SrmhwyJWDIS0mqQ77+rGIfwPnPxaBzm6LSoiL0W+K0WxaYJ9u9g |
| 215 | +uTiE4SmmyQbCOMCgsnfmCMdb1cTpt9VfiUyA8tTEx6/HHRitAdPVHE7/HyXxbkXc |
| 216 | +E0WWYq8B9rsnoCV5l8LC5bf75aI= |
| 217 | +-----END CERTIFICATE----- |
| 218 | +`) |
| 219 | +var serverKey = []byte(`-----BEGIN PRIVATE KEY----- |
| 220 | +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCqN3vIgsdnxc3K |
| 221 | +IWGYIvWezjLN9Y5ahuBPZcQKJUOIYunoT6AIM/DQdV+Z8KozetxPS/J7BiAEkuvj |
| 222 | +5EBktOh0IFfReRKF5e33awMAjJDi4+38K01ByK2DVVwgj3XqOnfclgERI4wjmhN9 |
| 223 | +bWhcfP1QZB9dfxe50rDW5BsqBTfTm9GeXT8aA6f4rWM8iqTixF0a3ygOOPoApxNs |
| 224 | +HM64tanTFWu4CPRJ8GSmumorjisEdtF+oHQV1u3kkVGlOuzV5eW8c3NQYkXVyP5N |
| 225 | +LQLdBO5NMc9anKlS3ixa4hw7d9MucletuwdaorNSiBCVYGdi6wf/YfSTDpQbslHD |
| 226 | +KAQguej9AgMBAAECggEAcvhq6XI8EcGvUDnf24yxboREGI0li8vSMo7ALUIiLTry |
| 227 | +VvGBXEkI/SRqYGYH8CGqMP1RWPs4IR7Dqff/7oWrBNTbvCcU9c/qPdXP/0zyh+4A |
| 228 | +TvVIh4huemjrgyqjMIQxdZL0QYlLHLjzNSLy/JWH3AbkkJhJhD/lJiCIoIdJv+lD |
| 229 | +W238dIrAX9Ed0BC0p7Ebf5r7asGY1cedGazqSj/T86R4YP1cFVKpGyodOY4w0JLS |
| 230 | +Nzoh6k40s+E4Ywcy+E+LyePslMxE9L+ZW4FG4AvXQC1gu4K/wtYSiWsy57e/7+m+ |
| 231 | +xj6hZPRsXkNnZGHWaIjR5uevAoZKjrp7GMk9u8EbGQKBgQDP6I/mq1TjIdwmaJRz |
| 232 | +SRb0O6HPay0es7Z2N/3EcEbosN30u1XSbs+bB44Y6s0xRsxj8sFc3wGQpOP6tGm7 |
| 233 | +rvafGnApjgqv15776bKWjJx4CE6yAKFjDYOM8i19HVtyUAF+RVU5kW0m4TzUT8wl |
| 234 | +3ywtM+ghLWC7b4lUIFti+b2V1wKBgQDRlvs67F5C3R+9vZzCBD2IPHBw28kQFPG1 |
| 235 | +Mk4jj3kX+kCOTuRyAefyl1LycdtSXr34e277bde9uVJhLPEgf87mn4QisfKT3sru |
| 236 | +vg0kJLesS7sZ4F0JytrUJ6hNKgJJWD+zEKtvl17VSLVk6uErDOqYrsJBW62b2asF |
| 237 | +jTbjKvK1SwKBgQC1WjjbjquXDBwKbMLA5Qpes/1q/iP3We9Yo3J5/S39Hvoc1aQA |
| 238 | +0KPKqQZr+bROvWDf9gpwxh2JXCt4rhJkojOBiQA5Xys3Qy/ssWcUJ0b89NIgNqiP |
| 239 | +zGPpd/3x2r+/sMX8rOGwO4gol+QFli2PA2J3c4WSGxD7rkjt1uOgLBQRNQKBgQDI |
| 240 | +kW+CB8h8vBcwAFAO6vfnc882cV2L4j8cYzObnCUJ6RX2GVFMOL66zE04bfSwcrHh |
| 241 | +JF4khg07JinLjLKDo0tgL67HdPrqvv38UitJN0n9u8slDCx8vn+DHyBUF6twfN8Y |
| 242 | +gQ9ODtFV0eqk1JD+HbIywqpq2UzeJAMhoO2xntv82QKBgQCY40cf/os4CY5Vx932 |
| 243 | +1T04bzbva5eS/3zUBcO7NVVcL7f+y/BgkWImuTtWNxQqlyQRVM78yiRaPBcGYoPP |
| 244 | +GWLtipfCWovkqbksXvEXR1gpISJCsmzgcwrsojvlyu8Zbb+kHI46j81QH9YGWPGI |
| 245 | +6p63faEx5JFQB6VS0ShqXlnZHg== |
| 246 | +-----END PRIVATE KEY-----`) |
| 247 | + |
| 248 | +var kubeConfig = []byte(`apiVersion: v1 |
| 249 | +clusters: |
| 250 | +- cluster: |
| 251 | + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeU1Ea3lPVEl3TWpnek1Gb1hEVE15TURreU5qSXdNamd6TUZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTFBqCldzdlNEM1kxR1daNGpPSDdqdm4zNUNKUUJvdm8vVnljN3BQdHB6OEVWaFJNNnpRSTMrU2EvdDZyMWdSeHcwM1QKalhHTlRvamNOU0dUVGhHSnN6K28vRjc0Tml5enN2bk5zaThHem9rRU42QmpVU1NmeDg2RVZrM3J4ekVkeFhEaQpoZmNmcDFrQkJBa3lyMGltUGlSZDBaWGFSTnA1dEhldDI3eXp4TTBLZDRjRUxPcHJQc1QzRlp0bGNQTU01YVhzCmhzcGR6dkpmMFNUeWtCNWRtUmU4WHVEc0VDeVgvSTBVbVdXNVkvaWRDMmN0WUE5bEExMjdlcEFrMFUwb29lU2IKUWdMZ0tScjJ6UUl0UzlVNEdEaUttdGVsZVI2dWg2bjdTRkxRZVl1Y25hYXZWb0lBZ1A0UDRRejVMVi9OelF3bApVZENFR3lPODkxQVVDVkIrbENNQ0F3RUFBYU5aTUZjd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZOTmc4L2ZueStjUW40YmZhdzVGRE1ld3kvcm9NQlVHQTFVZEVRUU8KTUF5Q0NtdDFZbVZ5Ym1WMFpYTXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRkFMSGZTcFNmTVQwQjlpTGFPVgpqN3d0V2I5MUY1Y25uZm15QWhVbm5qZXk2a1YyOFlNSWl2enZ3ZlQ5ZmVPb1llT3p4Tldla3YxUUVEaGw3a2pZCmJ2L2V5SDR3dDZGSHVCNlduQ1lESVpzK1doaXNURmd6bE5QeDJ0UVZLYjhzdy9WdGI0UU1WWVp3QjdpT2p6V1QKWjA4MW5MTmJpalQ3eEdIeWRWMWQ0SDR5eS91ajNJdWU3bkxYNHFPZk9udi8wQ2Vvb2Evd0VQeG1HMjJYb09WZgpzSlRWZnhrK1Zpak1Fc1kzRmZidWR1d3llNHc0cmxmUXhCNFZtbE1INEFrRmFvT1hLTGdGS3FrQkFLNVgwekhKClQvWWJkTm9jOThlcnJRNXZkRXhDZkV4RjFCWWtnbUVwcGZOV2UwK01xekgwZ2RTTTBzNEFBUmhrME4xNWRwVXoKeTBnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== |
| 252 | + server: https://test-cluster-api.nodomain.example.com:6443 |
| 253 | + name: kind-kind |
| 254 | +contexts: |
| 255 | +- context: |
| 256 | + cluster: kind-kind |
| 257 | + user: kind-kind |
| 258 | + name: kind-kind |
| 259 | +current-context: kind-kind |
| 260 | +kind: Config |
| 261 | +preferences: {} |
| 262 | +users: |
| 263 | +- name: kind-kind |
| 264 | + user: |
| 265 | + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJRUlJK3dnRVhXWVF3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TWpBNU1qa3lNREk0TXpCYUZ3MHlNekE1TWpreU1ESTRNekZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXFNcHJqYU1NaXdTN2dTOXgKcnd5VGFJRDZncUxOcklpMEh3SnRzTVpMRERTWTJLZWd5VCtwaDcxNjc4bHB3SHk2Njg3dVJ0WXpQcU90cXVNRQpEcTQzdmpxMzNMTng1ZVI4SnhSTk53d2Q3VXJZNmt4R2U1UUF3MXdWRW9OcmZTZk1BdTBOMEtIb1FKRjhEZDNlCjNFTVl5YmxySEYzMlN5MnluNHpWMmZRMDdpV2RUa2x3WDNZbkpTcFlFRTFDM3k2NjFHVVdGSXZCZm03b2NuOFUKeGdzQ01XNkxrbzVaMXh4OGVzZm5SSU5oZHZnS1BuN3dQSEtMUEQzRDNNUUdwM2V2QVVIWVExclpnTXRJNDQ3WQpVeVlkSFo5NDVLcVpRZ1ppS2FCdE1lblpmcVJndzArckNkeG5qMTRFYSt4RmtPMlNYQ0wrRWtNaUdvaFU3T29RCnVnay80d0lEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUVFlQUDM1OHZuRUorRzMyc09SUXpIc012Ngo2REFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBTklMSVp3TDhZdlh0QTRqZm9VNlBlYWc4bFBSaWQ2TDlEdTcxCm03NDZMRWQ0cVMvQ2VFb1Z3Q0JqUnplQytLRkcvMERFa3JvYXRTbzhQMXZQQVkxYm5yN3FJdmo3S0RIS25lWFcKSS9saGo4M3ZyYmhoRjN1TXVnTXRiaUI0cnB0eUxjMjc5cGpnWDJqMkFxN09OUDNnVVJoVmJBZG1JTmQwNlVhYQpnaTR1dFBGV1Z2cENsTlpKWXhqUnJVZzJCR0JSQ0RQVU9JWkVkeHBVRnQ5cWsrWWxva0RQb29lR1QzVGlKNnE3ClJwS01UQ04yOWo3cS96cEwxSlNGNXFEVWprdXV5eWd3aUNUcXR0SVBwajAvaU5kak9TVGJlcG5sMUdPOTVNTUEKbGN6NzQ4NEt1dTlGSEtTcjhvcHVEK1hWYXBRbWpuZVdIYmtQUVo4elMwTGExdHc1V2c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== |
| 266 | + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBcU1wcmphTU1pd1M3Z1M5eHJ3eVRhSUQ2Z3FMTnJJaTBId0p0c01aTEREU1kyS2VnCnlUK3BoNzE2NzhscHdIeTY2ODd1UnRZelBxT3RxdU1FRHE0M3ZqcTMzTE54NWVSOEp4Uk5Od3dkN1VyWTZreEcKZTVRQXcxd1ZFb05yZlNmTUF1ME4wS0hvUUpGOERkM2UzRU1ZeWJsckhGMzJTeTJ5bjR6VjJmUTA3aVdkVGtsdwpYM1luSlNwWUVFMUMzeTY2MUdVV0ZJdkJmbTdvY244VXhnc0NNVzZMa281WjF4eDhlc2ZuUklOaGR2Z0tQbjd3ClBIS0xQRDNEM01RR3AzZXZBVUhZUTFyWmdNdEk0NDdZVXlZZEhaOTQ1S3FaUWdaaUthQnRNZW5aZnFSZ3cwK3IKQ2R4bmoxNEVhK3hGa08yU1hDTCtFa01pR29oVTdPb1F1Z2svNHdJREFRQUJBb0lCQUNBTUJxMm1wbXdDb3dNZApHZTJOYXJOdHdhSnAvTGprWDZaL2xJbjZyQ2NPR1hNUktKTHRObWZpVHVRV0RyRVFQWUVtRWRGN085R0p6Q0JrCjU5Rk52S0d1amxnbDdkc2pMWHRSL0hNV0p0eDEySWRyb2ZvMm1JcC9BalU0cElEbnZIRlZ4c2kwNU43VmdJTTEKZStuQUI0WE5ZWXZLUDBmNHpqQkMwaHVHcFVJTnJTWEF5NEJUL0RQajF2bWkzQVZ5UGUwazNmV1RhY3RxRUN4dwpPUmRRMDhIeCtnRlNzNlpsYldZUU8xWnRlZ1AySlBKUVR3R0k5MGV3Q0JweCtNWC9Fdk5nRDdqbnhFS0ZRYUIzCko3RkpVVFIrcU5qZEs4c2wxeDhBUSs4R3lxVFo3SkNRRHI5WlRUamxBeW1UY0xNcHFSQXB3Z2hoYVZMNXlCejQKanBNODdIRUNnWUVBeENJdUVGMktFbFoxN29leE5RTUpyMGtBM2prbVVoMlpZbG5sOVdmdU1HNU1paldLNzZxNgpUWnVpVjB1c0dDandldDgvd1lvdEdHOFNqSFBsM2VXR0RzMTFJb1doRmdrK29keTNIbU5Gbm9wVWVSbmVxVnNvCnJLT0I0VGpuVjJpdkl2M1FuclFpV2NETjNGd0JoczlYNlQ0ZUwrSUZrVkk4LzdJUDRlbVlmNHNDZ1lFQTNFK3UKSkxZVHZKYm9YU2l5cHJlVVlnZGs1UjdlNVMvK3FNczJPOTh4d3hQRVgvbmxER0FWZlBXMEJ4ODhTMjk2c1dtTQpqYy8xdW95cDJrTWRBZm9DdVVYSDZncGFZVU1qSlpwQ01Vd1dyUDVuTGFQejhJMjZMQzBtY3M0T3JJNjI3MnFXCm5wQ3d1T1VMbzYyYVZrVWJDVGlmMkg0NkNHNkhUY1JGaHB0ZXpBa0NnWUVBaWtkQ3pMejJCRm02eVpJWFNMVzgKbFQxV0JGYXNnc1psaHFhMDd5RDRHR01iU1hIWVk0S3QyTnQ2U0N1TXlIZk1uQVJiMGRyV1VseTA2aHNvSEJxZgpPajUyY0FGZ2djWEF4Nk54NDFYQUZyZVdPTThaWWJOb2FOYmFVZXlwaGNIRGdGc01RMmZpcy82djVNVmxPaU5pCjZvbW1CTUpJaEoxRGJrNmV6ZnJBVG1NQ2dZRUFnSGl5bTFQV0ZJNkh0L09Jb25IQlJKejlPQ01WWmQ3a0NQaGYKaXZCdnEwdDJvMlV0TFZkR2tKVVRRMmZ5bUNiTkRISDVkYVVFcmFGalZ4VDE4SFlqYW5rSHlESDdYR1p6TTNWTwpEa05Kb2QzRXV6ZTFnOXlSNlRyM0JkR2xldmpLTXJrY1ZpRVgvT29NTEltS3k2NEd3d3pUSWNNU0FtSzU0aDZIClVLUi8xa2tDZ1lCQTN2R1lDTlJDS2hIazdKLzZBVnpESVN2VVMvQk9ma0pMTUplcDZ2cWt2SHl1bU9ISkVVcjAKa25KNVJHY3NqY3VsUE1EM2F1TjJlWWovV1k3dkJIclBiSk9sRkFlUVpCc2dKTEg5ZXlzV29tY1haNzRNQ0tUegpUTXhMWDhhZG9Sa3Y0NnhCdlB0YzR2WWVJUWErVWFxRDhVTDY3S3NaWnJVekdDdVRNdnIwWEE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= |
| 267 | +
|
| 268 | +`) |
0 commit comments