Skip to content

Commit e870085

Browse files
committed
Add unit test to find and create oidc provider
1 parent 99e516e commit e870085

File tree

2 files changed

+270
-2
lines changed

2 files changed

+270
-2
lines changed

pkg/cloud/services/eks/iam/iam.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"crypto/sha1"
2121
"encoding/hex"
2222
"encoding/json"
23-
"fmt"
2423
"net/http"
2524
"net/url"
2625

@@ -459,7 +458,8 @@ func (s *IAMService) FindAndVerifyOIDCProvider(cluster *eks.Cluster) (string, er
459458
if err != nil {
460459
return "", errors.Wrap(err, "error getting provider")
461460
}
462-
if fmt.Sprintf("https://%s", *provider.Url) != issuerURL.String() {
461+
// URL should always contain `https`.
462+
if *provider.Url != issuerURL.String() {
463463
continue
464464
}
465465
if len(provider.ThumbprintList) != 1 || *provider.ThumbprintList[0] != thumbprint {

pkg/cloud/services/eks/oidc_test.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)