Skip to content

Commit c31d9cd

Browse files
committed
feat: Add AWS instance identity authentication
This allows zero-trust authentication for all AWS instances. Prior to this, AWS instances could be used by passing `CODER_TOKEN` as an environment variable to the startup script. AWS explicitly states that secrets should not be passed in startup scripts because it's user-readable.
1 parent 591523a commit c31d9cd

File tree

10 files changed

+580
-34
lines changed

10 files changed

+580
-34
lines changed

cli/workspaceagent.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"net/http"
56
"net/url"
67
"os"
78
"time"
@@ -38,6 +39,11 @@ func workspaceAgent() *cobra.Command {
3839
}
3940
logger := slog.Make(sloghuman.Sink(cmd.OutOrStdout())).Leveled(slog.LevelDebug)
4041
client := codersdk.New(coderURL)
42+
43+
// exchangeToken returns a session token.
44+
// This is abstracted to allow for the same looping condition
45+
// regardless of instance identity auth type.
46+
var exchangeToken func(context.Context) (codersdk.WorkspaceAgentAuthenticateResponse, error)
4147
switch auth {
4248
case "token":
4349
sessionToken, exists := os.LookupEnv("CODER_TOKEN")
@@ -53,29 +59,48 @@ func workspaceAgent() *cobra.Command {
5359
if gcpClientRaw != nil {
5460
gcpClient, _ = gcpClientRaw.(*metadata.Client)
5561
}
62+
exchangeToken = func(ctx context.Context) (codersdk.WorkspaceAgentAuthenticateResponse, error) {
63+
return client.AuthWorkspaceGoogleInstanceIdentity(ctx, "", gcpClient)
64+
}
65+
case "aws-instance-identity":
66+
// This is *only* done for testing to mock client authentication.
67+
// This will never be set in a production scenario.
68+
var awsClient *http.Client
69+
awsClientRaw := cmd.Context().Value("aws-client")
70+
if awsClientRaw != nil {
71+
awsClient, _ = awsClientRaw.(*http.Client)
72+
}
73+
exchangeToken = func(ctx context.Context) (codersdk.WorkspaceAgentAuthenticateResponse, error) {
74+
return client.AuthWorkspaceAWSInstanceIdentity(ctx, awsClient)
75+
}
76+
case "azure-instance-identity":
77+
return xerrors.Errorf("not implemented")
78+
}
5679

57-
ctx, cancelFunc := context.WithTimeout(cmd.Context(), 30*time.Second)
80+
if exchangeToken != nil {
81+
// Agent's can start before resources are returned from the provisioner
82+
// daemon. If there are many resources being provisioned, this time
83+
// could be significant. This is arbitrarily set at an hour to prevent
84+
// tons of idle agents from pinging coderd.
85+
ctx, cancelFunc := context.WithTimeout(cmd.Context(), time.Hour)
5886
defer cancelFunc()
5987
for retry.New(100*time.Millisecond, 5*time.Second).Wait(ctx) {
6088
var response codersdk.WorkspaceAgentAuthenticateResponse
6189

62-
response, err = client.AuthWorkspaceGoogleInstanceIdentity(ctx, "", gcpClient)
90+
response, err = exchangeToken(ctx)
6391
if err != nil {
64-
logger.Warn(ctx, "authenticate workspace with Google Instance Identity", slog.Error(err))
92+
logger.Warn(ctx, "authenticate workspace", slog.F("method", auth), slog.Error(err))
6593
continue
6694
}
6795
client.SessionToken = response.SessionToken
68-
logger.Info(ctx, "authenticated with Google Instance Identity")
96+
logger.Info(ctx, "authenticated", slog.F("method", auth))
6997
break
7098
}
7199
if err != nil {
72100
return xerrors.Errorf("agent failed to authenticate in time: %w", err)
73101
}
74-
case "aws-instance-identity":
75-
return xerrors.Errorf("not implemented")
76-
case "azure-instance-identity":
77-
return xerrors.Errorf("not implemented")
78102
}
103+
79104
closer := agent.New(client.ListenWorkspaceAgent, &peer.ConnOptions{
80105
Logger: logger,
81106
})

cli/workspaceagent_test.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,66 @@ import (
1414

1515
func TestWorkspaceAgent(t *testing.T) {
1616
t.Parallel()
17+
t.Run("AWS", func(t *testing.T) {
18+
t.Parallel()
19+
instanceID := "instanceidentifier"
20+
certificates, metadataClient := coderdtest.NewAWSInstanceIdentity(t, instanceID)
21+
client := coderdtest.New(t, &coderdtest.Options{
22+
AWSInstanceIdentity: certificates,
23+
})
24+
user := coderdtest.CreateFirstUser(t, client)
25+
coderdtest.NewProvisionerDaemon(t, client)
26+
version := coderdtest.CreateProjectVersion(t, client, user.OrganizationID, &echo.Responses{
27+
Parse: echo.ParseComplete,
28+
Provision: []*proto.Provision_Response{{
29+
Type: &proto.Provision_Response_Complete{
30+
Complete: &proto.Provision_Complete{
31+
Resources: []*proto.Resource{{
32+
Name: "somename",
33+
Type: "someinstance",
34+
Agent: &proto.Agent{
35+
Auth: &proto.Agent_InstanceId{
36+
InstanceId: instanceID,
37+
},
38+
},
39+
}},
40+
},
41+
},
42+
}},
43+
})
44+
project := coderdtest.CreateProject(t, client, user.OrganizationID, version.ID)
45+
coderdtest.AwaitProjectVersionJob(t, client, version.ID)
46+
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
47+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
48+
49+
cmd, _ := clitest.New(t, "workspaces", "agent", "--auth", "aws-instance-identity", "--url", client.URL.String())
50+
ctx, cancelFunc := context.WithCancel(context.Background())
51+
defer cancelFunc()
52+
go func() {
53+
// A linting error occurs for weakly typing the context value here,
54+
// but it seems reasonable for a one-off test.
55+
// nolint
56+
ctx = context.WithValue(ctx, "aws-client", metadataClient)
57+
err := cmd.ExecuteContext(ctx)
58+
require.NoError(t, err)
59+
}()
60+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
61+
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
62+
require.NoError(t, err)
63+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].ID, nil, nil)
64+
require.NoError(t, err)
65+
defer dialer.Close()
66+
_, err = dialer.Ping()
67+
require.NoError(t, err)
68+
cancelFunc()
69+
})
70+
1771
t.Run("GoogleCloud", func(t *testing.T) {
1872
t.Parallel()
1973
instanceID := "instanceidentifier"
2074
validator, metadata := coderdtest.NewGoogleInstanceIdentity(t, instanceID, false)
2175
client := coderdtest.New(t, &coderdtest.Options{
22-
GoogleTokenValidator: validator,
76+
GoogleInstanceIdentity: validator,
2377
})
2478
user := coderdtest.CreateFirstUser(t, client)
2579
coderdtest.NewProvisionerDaemon(t, client)

coderd/awsidentity/awsidentity.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package awsidentity
2+
3+
import (
4+
"crypto"
5+
"crypto/rsa"
6+
"crypto/sha256"
7+
"crypto/x509"
8+
"encoding/base64"
9+
"encoding/json"
10+
"encoding/pem"
11+
12+
"golang.org/x/xerrors"
13+
)
14+
15+
// Region represents the AWS locations a public-key covers.
16+
type Region string
17+
18+
const (
19+
Other Region = "other"
20+
HongKong Region = "hongkong"
21+
Bahrain Region = "bahrain"
22+
CapeTown Region = "capetown"
23+
Milan Region = "milan"
24+
China Region = "china"
25+
GovCloud Region = "govcloud"
26+
)
27+
28+
var (
29+
All = []Region{Other, HongKong, Bahrain, CapeTown, Milan, China, GovCloud}
30+
)
31+
32+
// Certificates hold public keys for various AWS regions. See:
33+
type Certificates map[Region]string
34+
35+
// Identity represents a validated document and signature.
36+
type Identity struct {
37+
InstanceID string
38+
Region Region
39+
}
40+
41+
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
42+
type awsInstanceIdentityDocument struct {
43+
InstanceID string `json:"instanceId"`
44+
}
45+
46+
// Validate ensures the document was signed by an AWS public key.
47+
// Regions that aren't provided in certificates will use defaults.
48+
func Validate(signature, document string, certificates Certificates) (Identity, error) {
49+
if certificates == nil {
50+
certificates = Certificates{}
51+
}
52+
for _, region := range All {
53+
if _, ok := certificates[region]; ok {
54+
continue
55+
}
56+
defaultCertificate, exists := defaultCertificates[region]
57+
if !exists {
58+
panic("dev error: no certificate exists for region " + region)
59+
}
60+
certificates[region] = defaultCertificate
61+
}
62+
63+
var instanceIdentity awsInstanceIdentityDocument
64+
err := json.Unmarshal([]byte(document), &instanceIdentity)
65+
if err != nil {
66+
return Identity{}, xerrors.Errorf("parse document: %w", err)
67+
}
68+
rawSignature, err := base64.StdEncoding.DecodeString(signature)
69+
if err != nil {
70+
return Identity{}, xerrors.Errorf("decode signature: %w", err)
71+
}
72+
documentHash := sha256.New()
73+
_, err = documentHash.Write([]byte(document))
74+
if err != nil {
75+
return Identity{}, xerrors.Errorf("write document hash: %w", err)
76+
}
77+
hashedDocument := documentHash.Sum(nil)
78+
79+
for region, certificate := range certificates {
80+
regionBlock, rest := pem.Decode([]byte(certificate))
81+
if len(rest) != 0 {
82+
return Identity{}, xerrors.Errorf("invalid certificate for %q. %d bytes remain", region, len(rest))
83+
}
84+
regionCert, err := x509.ParseCertificate(regionBlock.Bytes)
85+
if err != nil {
86+
return Identity{}, xerrors.Errorf("parse certificate: %w", err)
87+
}
88+
regionPublicKey, valid := regionCert.PublicKey.(*rsa.PublicKey)
89+
if !valid {
90+
return Identity{}, xerrors.Errorf("certificate for %q was not an rsa key", region)
91+
}
92+
err = rsa.VerifyPKCS1v15(regionPublicKey, crypto.SHA256, hashedDocument, rawSignature)
93+
if err != nil {
94+
continue
95+
}
96+
return Identity{
97+
InstanceID: instanceIdentity.InstanceID,
98+
Region: region,
99+
}, nil
100+
}
101+
return Identity{}, rsa.ErrVerification
102+
}
103+
104+
// Default AWS certificates for regions.
105+
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html
106+
var defaultCertificates = Certificates{
107+
Other: `-----BEGIN CERTIFICATE-----
108+
MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV
109+
BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw
110+
FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu
111+
Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC
112+
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV
113+
BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w
114+
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3
115+
e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD
116+
jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL
117+
XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs
118+
77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq
119+
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh
120+
dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h
121+
em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
122+
BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T
123+
C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ
124+
7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0=
125+
-----END CERTIFICATE-----`,
126+
HongKong: `-----BEGIN CERTIFICATE-----
127+
MIICSzCCAbQCCQDtQvkVxRvK9TANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJV
128+
UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEYMBYGA1UE
129+
ChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1hem9uYXdzLmNvbTAe
130+
Fw0xOTAyMDMwMzAwMDZaFw0yOTAyMDIwMzAwMDZaMGoxCzAJBgNVBAYTAlVTMRMw
131+
EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgwFgYDVQQKEw9B
132+
bWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3MuY29tMIGfMA0G
133+
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1kkHXYTfc7gY5Q55JJhjTieHAgacaQkiR
134+
Pity9QPDE3b+NXDh4UdP1xdIw73JcIIG3sG9RhWiXVCHh6KkuCTqJfPUknIKk8vs
135+
M3RXflUpBe8Pf+P92pxqPMCz1Fr2NehS3JhhpkCZVGxxwLC5gaG0Lr4rFORubjYY
136+
Rh84dK98VwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAA6xV9f0HMqXjPHuGILDyaNN
137+
dKcvplNFwDTydVg32MNubAGnecoEBtUPtxBsLoVYXCOb+b5/ZMDubPF9tU/vSXuo
138+
TpYM5Bq57gJzDRaBOntQbX9bgHiUxw6XZWaTS/6xjRJDT5p3S1E0mPI3lP/eJv4o
139+
Ezk5zb3eIf10/sqt4756
140+
-----END CERTIFICATE-----`,
141+
Bahrain: `-----BEGIN CERTIFICATE-----
142+
MIIDPDCCAqWgAwIBAgIJAMl6uIV/zqJFMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNV
143+
BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSAw
144+
HgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzEaMBgGA1UEAwwRZWMyLmFt
145+
YXpvbmF3cy5jb20wIBcNMTkwNDI2MTQzMjQ3WhgPMjE5ODA5MjkxNDMyNDdaMHIx
146+
CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0
147+
dGxlMSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzEaMBgGA1UEAwwR
148+
ZWMyLmFtYXpvbmF3cy5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVN
149+
CDTZEnIeoX1SEYqq6k1BV0ZlpY5y3KnoOreCAE589TwS4MX5+8Fzd6AmACmugeBP
150+
Qk7Hm6b2+g/d4tWycyxLaQlcq81DB1GmXehRkZRgGeRge1ePWd1TUA0I8P/QBT7S
151+
gUePm/kANSFU+P7s7u1NNl+vynyi0wUUrw7/wIZTAgMBAAGjgdcwgdQwHQYDVR0O
152+
BBYEFILtMd+T4YgH1cgc+hVsVOV+480FMIGkBgNVHSMEgZwwgZmAFILtMd+T4YgH
153+
1cgc+hVsVOV+480FoXakdDByMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGlu
154+
Z3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEgMB4GA1UECgwXQW1hem9uIFdlYiBTZXJ2
155+
aWNlcyBMTEMxGjAYBgNVBAMMEWVjMi5hbWF6b25hd3MuY29tggkAyXq4hX/OokUw
156+
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBhkNTBIFgWFd+ZhC/LhRUY
157+
4OjEiykmbEp6hlzQ79T0Tfbn5A4NYDI2icBP0+hmf6qSnIhwJF6typyd1yPK5Fqt
158+
NTpxxcXmUKquX+pHmIkK1LKDO8rNE84jqxrxRsfDi6by82fjVYf2pgjJW8R1FAw+
159+
mL5WQRFexbfB5aXhcMo0AA==
160+
-----END CERTIFICATE-----`,
161+
CapeTown: `-----BEGIN CERTIFICATE-----
162+
MIICNjCCAZ+gAwIBAgIJAKumfZiRrNvHMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
163+
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
164+
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0xOTExMjcw
165+
NzE0MDVaGA8yMTk5MDUwMjA3MTQwNVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgT
166+
EFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0Ft
167+
YXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
168+
gQDFd571nUzVtke3rPyRkYfvs3jh0C0EMzzG72boyUNjnfw1+m0TeFraTLKb9T6F
169+
7TuB/ZEN+vmlYqr2+5Va8U8qLbPF0bRH+FdaKjhgWZdYXxGzQzU3ioy5W5ZM1VyB
170+
7iUsxEAlxsybC3ziPYaHI42UiTkQNahmoroNeqVyHNnBpQIDAQABMA0GCSqGSIb3
171+
DQEBCwUAA4GBAAJLylWyElEgOpW4B1XPyRVD4pAds8Guw2+krgqkY0HxLCdjosuH
172+
RytGDGN+q75aAoXzW5a7SGpxLxk6Hfv0xp3RjDHsoeP0i1d8MD3hAC5ezxS4oukK
173+
s5gbPOnokhKTMPXbTdRn5ZifCbWlx+bYN/mTYKvxho7b5SVg2o1La9aK
174+
-----END CERTIFICATE-----`,
175+
Milan: `-----BEGIN CERTIFICATE-----
176+
MIICNjCCAZ+gAwIBAgIJAOZ3GEIaDcugMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
177+
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
178+
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0xOTEwMjQx
179+
NTE5MDlaGA8yMTk5MDMyOTE1MTkwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgT
180+
EFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0Ft
181+
YXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
182+
gQCjiPgW3vsXRj4JoA16WQDyoPc/eh3QBARaApJEc4nPIGoUolpAXcjFhWplo2O+
183+
ivgfCsc4AU9OpYdAPha3spLey/bhHPRi1JZHRNqScKP0hzsCNmKhfnZTIEQCFvsp
184+
DRp4zr91/WS06/flJFBYJ6JHhp0KwM81XQG59lV6kkoW7QIDAQABMA0GCSqGSIb3
185+
DQEBCwUAA4GBAGLLrY3P+HH6C57dYgtJkuGZGT2+rMkk2n81/abzTJvsqRqGRrWv
186+
XRKRXlKdM/dfiuYGokDGxiC0Mg6TYy6wvsR2qRhtXW1OtZkiHWcQCnOttz+8vpew
187+
wx8JGMvowtuKB1iMsbwyRqZkFYLcvH+Opfb/Aayi20/ChQLdI6M2R5VU
188+
-----END CERTIFICATE-----`,
189+
China: `-----BEGIN CERTIFICATE-----
190+
MIICSzCCAbQCCQCQu97teKRD4zANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJV
191+
UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEYMBYGA1UE
192+
ChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1hem9uYXdzLmNvbTAe
193+
Fw0xMzA4MjExMzIyNDNaFw0yMzA4MjExMzIyNDNaMGoxCzAJBgNVBAYTAlVTMRMw
194+
EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgwFgYDVQQKEw9B
195+
bWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3MuY29tMIGfMA0G
196+
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GFQ2WoBl1xZYH85INUMaTc4D30QXM6f+
197+
YmWZyJD9fC7Z0UlaZIKoQATqCO58KNCre+jECELYIX56Uq0lb8LRLP8tijrQ9Sp3
198+
qJcXiH66kH0eQ44a5YdewcFOy+CSAYDUIaB6XhTQJ2r7bd4A2vw3ybbxTOWONKdO
199+
WtgIe3M3iwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAHzQC5XZVeuD9GTJTsbO5AyH
200+
ZQvki/jfARNrD9dgBRYZzLC/NOkWG6M9wlrmks9RtdNxc53nLxKq4I2Dd73gI0yQ
201+
wYu9YYwmM/LMgmPlI33Rg2Ohwq4DVgT3hO170PL6Fsgiq3dMvctSImJvjWktBQaT
202+
bcAgaZLHGIpXPrWSA2d+
203+
-----END CERTIFICATE-----`,
204+
GovCloud: `-----BEGIN CERTIFICATE-----
205+
MIIDCzCCAnSgAwIBAgIJAIe9Hnq82O7UMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV
206+
BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0
207+
dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMTA3MTQx
208+
NDI3NTdaFw0yNDA3MTMxNDI3NTdaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX
209+
YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6
210+
b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
211+
qaIcGFFTx/SO1W5G91jHvyQdGP25n1Y91aXCuOOWAUTvSvNGpXrI4AXNrQF+CmIO
212+
C4beBASnHCx082jYudWBBl9Wiza0psYc9flrczSzVLMmN8w/c78F/95NfiQdnUQP
213+
pvgqcMeJo82cgHkLR7XoFWgMrZJqrcUK0gnsQcb6kakCAwEAAaOB1DCB0TALBgNV
214+
HQ8EBAMCB4AwHQYDVR0OBBYEFNWV53gWJz72F5B1ZVY4O/dfFYBPMIGOBgNVHSME
215+
gYYwgYOAFNWV53gWJz72F5B1ZVY4O/dfFYBPoWCkXjBcMQswCQYDVQQGEwJVUzEZ
216+
MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G
217+
A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQCHvR56vNju1DASBgNVHRMB
218+
Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz
219+
M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2
220+
xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle
221+
Rwi/S3BFXISixNx9cILu
222+
-----END CERTIFICATE-----`,
223+
}

0 commit comments

Comments
 (0)