• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oxyno-zeta / s3-proxy / 14254130052

03 Apr 2025 10:40PM UTC coverage: 86.162%. Remained the same
14254130052

Pull #560

github

web-flow
fix(deps): update module github.com/coreos/go-oidc/v3 to v3.14.1
Pull Request #560: fix(deps): update module github.com/coreos/go-oidc/v3 to v3.14.1

5417 of 6287 relevant lines covered (86.16%)

46.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.94
/pkg/s3-proxy/utils/generalutils/utils.go
1
package generalutils
2

3
import (
4
        "crypto/tls"
5
        "fmt"
6
        "io"
7
        "net"
8
        "net/http"
9
        "net/url"
10
        "os"
11
        "strings"
12
        "time"
13

14
        "emperror.dev/errors"
15
        "github.com/aws/aws-sdk-go/aws"
16
        "github.com/aws/aws-sdk-go/aws/arn"
17
        "github.com/aws/aws-sdk-go/aws/credentials"
18
        "github.com/aws/aws-sdk-go/aws/session"
19
        "github.com/aws/aws-sdk-go/service/s3"
20
        "github.com/aws/aws-sdk-go/service/secretsmanager"
21
        "github.com/aws/aws-sdk-go/service/ssm"
22
        "github.com/aws/aws-sdk-go/service/sts"
23
)
24

25
const (
26
        schemeARN   string = "arn"
27
        schemeFile  string = "file"
28
        schemeHTTP  string = "http"
29
        schemeHTTPS string = "https"
30
        schemeS3    string = "s3"
31

32
        serviceS3             string = "s3"
33
        serviceSecretsManager string = "secretsmanager"
34
        serviceSSM            string = "ssm"
35
)
36

37
// ClientIP will return client ip from request.
38
func ClientIP(r *http.Request) string {
134✔
39
        ipAddress := r.Header.Get("X-Real-Ip")
134✔
40
        if ipAddress == "" {
268✔
41
                ipAddress = r.Header.Get("X-Forwarded-For")
134✔
42
        }
134✔
43

44
        if ipAddress == "" {
268✔
45
                ipAddress = r.RemoteAddr
134✔
46
        }
134✔
47

48
        return ipAddress
134✔
49
}
50

51
func GetRequestScheme(r *http.Request) string {
289✔
52
        // Get forwarded scheme
289✔
53
        fwdScheme := r.Header.Get("X-Forwarded-Proto")
289✔
54
        // Check if it is https
289✔
55
        if r.TLS != nil || fwdScheme == "https" {
306✔
56
                return "https"
17✔
57
        }
17✔
58

59
        // RFC 7239
60
        forwardedH := r.Header.Get("Forwarded")
271✔
61
        proto, _ := parseForwarded(forwardedH)
271✔
62
        // Check if protocol have been found
271✔
63
        if proto != "" {
273✔
64
                return proto
2✔
65
        }
2✔
66

67
        // Default
68
        return "http"
269✔
69
}
70

71
func GetRequestURI(r *http.Request) string {
136✔
72
        scheme := GetRequestScheme(r)
136✔
73

136✔
74
        return fmt.Sprintf("%s://%s%s", scheme, GetRequestHost(r), r.URL.RequestURI())
136✔
75
}
136✔
76

77
func GetRequestHost(r *http.Request) string {
415✔
78
        // not standard, but most popular
415✔
79
        host := r.Header.Get("X-Forwarded-Host")
415✔
80
        if host != "" {
417✔
81
                return host
2✔
82
        }
2✔
83

84
        // RFC 7239
85
        forwardedH := r.Header.Get("Forwarded")
412✔
86
        _, host = parseForwarded(forwardedH)
412✔
87

412✔
88
        if host != "" {
413✔
89
                return host
1✔
90
        }
1✔
91

92
        // if all else fails fall back to request host
93
        host = r.Host
411✔
94

411✔
95
        return host
411✔
96
}
97

98
func parseForwarded(forwarded string) (proto, host string) {
684✔
99
        if forwarded == "" {
1,364✔
100
                return
680✔
101
        }
680✔
102

103
        for _, forwardedPair := range strings.Split(forwarded, ";") {
15✔
104
                if tv := strings.SplitN(forwardedPair, "=", 2); len(tv) == 2 { //nolint: mnd // No constant for that
24✔
105
                        token, value := tv[0], tv[1]
12✔
106
                        token = strings.TrimSpace(token)
12✔
107
                        value = strings.TrimSpace(strings.Trim(value, `"`))
12✔
108

12✔
109
                        switch strings.ToLower(token) {
12✔
110
                        case "proto":
3✔
111
                                proto = value
3✔
112
                        case "host":
3✔
113
                                host = value
3✔
114
                        }
115
                }
116
        }
117

118
        return
3✔
119
}
120

121
// ParseCipherSuite parses a cipher suite name into the tls package cipher suite id.
122
//
123
// If the name is not recognized, 0 is returned.
124
func ParseCipherSuite(suiteName string) uint16 {
6✔
125
        for _, suite := range tls.CipherSuites() {
36✔
126
                if suite.Name == suiteName {
34✔
127
                        return suite.ID
4✔
128
                }
4✔
129
        }
130

131
        return 0
2✔
132
}
133

134
// ParseTLSVersion parses the TLS version number from a string. This accepts raw version numbers
135
// "1.0", "1.1", "1.2", "1.3". If the string is prefixed with "TLS ", "TLSv", "TLS-", or "TLS_"
136
// (case-insensitive), that prefix is removed. The decimal separator ('.') can be replaced with
137
// either a '_' or a '-'.
138
//
139
// For example: "TLSv1.2", "TLS_1-2", "tls-1_2", "TLs 1-2", etc., are equivalent and return
140
// tls.VersionTLS12.
141
//
142
// If the version number cannot be parsed, 0 is returned.
143
func ParseTLSVersion(tlsVersionString string) uint16 {
141✔
144
        tlsVersionString = strings.ToLower(tlsVersionString)
141✔
145

141✔
146
        if strings.HasPrefix(tlsVersionString, "tlsv") {
174✔
147
                tlsVersionString = tlsVersionString[4:]
33✔
148
        } else if strings.HasPrefix(tlsVersionString, "tls") {
232✔
149
                tlsVersionString = tlsVersionString[3:]
91✔
150

91✔
151
                if len(tlsVersionString) == 0 {
92✔
152
                        return 0
1✔
153
                }
1✔
154

155
                // Remove a dash, underscore, or space.
156
                if tlsVersionString[0] == '-' || tlsVersionString[0] == '_' || tlsVersionString[0] == ' ' {
151✔
157
                        tlsVersionString = tlsVersionString[1:]
61✔
158
                }
61✔
159
        }
160

161
        tlsVersionString = strings.ReplaceAll(tlsVersionString, "_", ".")
140✔
162
        tlsVersionString = strings.ReplaceAll(tlsVersionString, "-", ".")
140✔
163

140✔
164
        switch tlsVersionString {
140✔
165
        case "1.0":
30✔
166
                return tls.VersionTLS10
30✔
167
        case "1.1":
35✔
168
                return tls.VersionTLS11
35✔
169
        case "1.2":
36✔
170
                return tls.VersionTLS12
36✔
171
        case "1.3":
31✔
172
                return tls.VersionTLS13
31✔
173
        }
174

175
        return 0
8✔
176
}
177

178
// GetDocumentFromURLOption is a type alias for a function that can set various options to GetDocumentFromURL.
179
type GetDocumentFromURLOption func(awsCfg *aws.Config, httpClient *http.Client)
180

181
// WithAWSEndpoint is an option for GetDocumentFromURL to set the AWS service endpoint.
182
func WithAWSEndpoint(endpoint string) GetDocumentFromURLOption {
8✔
183
        return func(awsCfg *aws.Config, _ *http.Client) {
18✔
184
                if awsCfg != nil {
18✔
185
                        awsCfg.Endpoint = aws.String(endpoint)
8✔
186
                }
8✔
187
        }
188
}
189

190
// WithAWSRegion is an option for GetDocumentFromURL to set the AWS region.
191
func WithAWSRegion(region string) GetDocumentFromURLOption {
8✔
192
        return func(awsCfg *aws.Config, _ *http.Client) {
18✔
193
                if awsCfg != nil {
18✔
194
                        awsCfg.Region = aws.String(region)
8✔
195
                }
8✔
196
        }
197
}
198

199
// WithAWSDisableSSL is an option for GetDocumentFromURL to optionally disable SSL for requests.
200
func WithAWSDisableSSL(disableSSL bool) GetDocumentFromURLOption {
7✔
201
        return func(awsCfg *aws.Config, _ *http.Client) {
16✔
202
                if awsCfg != nil {
16✔
203
                        awsCfg.DisableSSL = aws.Bool(disableSSL)
7✔
204
                }
7✔
205
        }
206
}
207

208
// WithAWSStaticCredentials is an option for GetDocumentFromURL to set AWS credentials.
209
func WithAWSStaticCredentials(accessKey, secretKey, token string) GetDocumentFromURLOption {
7✔
210
        return func(awsCfg *aws.Config, _ *http.Client) {
16✔
211
                if awsCfg != nil {
16✔
212
                        awsCfg.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, token)
7✔
213
                }
7✔
214
        }
215
}
216

217
// WithHTTPTimeout is an option for GetDocumentFromURL to set the HTTP timeout.
218
func WithHTTPTimeout(timeout time.Duration) GetDocumentFromURLOption {
9✔
219
        return func(awsCfg *aws.Config, httpClient *http.Client) {
20✔
220
                if awsCfg != nil {
18✔
221
                        if awsCfg.HTTPClient == nil {
14✔
222
                                awsCfg.HTTPClient = &http.Client{}
7✔
223
                        }
7✔
224

225
                        awsCfg.HTTPClient.Timeout = timeout
7✔
226
                }
227

228
                if httpClient != nil {
15✔
229
                        httpClient.Timeout = timeout
4✔
230
                }
4✔
231
        }
232
}
233

234
// GetDocumentFromURL retrieves a textual document from a URL, which may be an AWS ARN for an S3 object,
235
// Secrets Manager secret, or  Systems Manager parameter (arn:...); an HTTP or HTTPS URL; an S3 URL in
236
// the form s3://bucket/key; or a file in URL or regular path form.
237
func GetDocumentFromURL(rawURL string, opts ...GetDocumentFromURLOption) ([]byte, error) {
25✔
238
        parsedURL, err := url.Parse(rawURL)
25✔
239
        if err != nil {
26✔
240
                return nil, err
1✔
241
        }
1✔
242

243
        switch parsedURL.Scheme {
24✔
244
        case "":
2✔
245
                return getDocumentFromFile(rawURL)
2✔
246
        case schemeARN:
6✔
247
                return getDocumentFromARN(rawURL, opts...)
6✔
248
        case schemeFile:
4✔
249
                if parsedURL.RawQuery != "" {
5✔
250
                        return nil, errors.New("file URL cannot contain query")
1✔
251
                }
1✔
252

253
                if parsedURL.Fragment != "" {
4✔
254
                        return nil, errors.New("file URL cannot contain fragment")
1✔
255
                }
1✔
256

257
                return getDocumentFromFile(parsedURL.Path)
2✔
258
        case schemeHTTP, schemeHTTPS:
5✔
259
                if parsedURL.Fragment != "" {
6✔
260
                        return nil, errors.Errorf("%s URL cannot contain fragment", parsedURL.Scheme)
1✔
261
                }
1✔
262

263
                return getDocumentFromHTTP(rawURL, opts...)
4✔
264
        case schemeS3:
5✔
265
                if !strings.Contains(parsedURL.Path, "/") {
6✔
266
                        return nil, errors.New("missing S3 key")
1✔
267
                }
1✔
268

269
                return getDocumentFromS3(parsedURL.Host, parsedURL.Path, opts...)
4✔
270
        }
271

272
        return nil, errors.Errorf("unsupported URL scheme: %s", parsedURL.Scheme)
2✔
273
}
274

275
// getDocumentFromARN retrieves a textual document from an AWS ARN for an S3 object, Secrets Manager secret, or
276
// Systems Manager parameter.
277
//
278
// Note that S3 objects are usually supplied in S3 URL form (s3://bucket/key) instead, which is handled by
279
// GetDocumentByURL directly.
280
func getDocumentFromARN(rawURL string, opts ...GetDocumentFromURLOption) ([]byte, error) {
6✔
281
        docARN, err := validateDocumentARN(rawURL)
6✔
282
        if err != nil {
10✔
283
                return nil, err
4✔
284
        }
4✔
285

286
        // Service and resource has already been validated here.
287
        switch docARN.Service {
2✔
288
        case serviceS3:
2✔
289
                parts := strings.SplitN(docARN.Resource, "/", 2) //nolint:mnd // Splitting once
2✔
290
                if len(parts) != 2 {                             //nolint:mnd // Splitting once
2✔
291
                        // Should not get here; covered by validateDocumentARN.
×
292
                        return nil, errors.New("missing S3 key")
×
293
                }
×
294

295
                return getDocumentFromS3(parts[0], parts[1], opts...)
2✔
296

297
        case serviceSecretsManager:
×
298
                return getDocumentFromSecretsManager(docARN, opts...)
×
299

300
        case serviceSSM:
×
301
                return getDocumentFromSSM(docARN, opts...)
×
302

303
        default:
×
304
                // Should not get here; covered by validateDocumentARN.
×
305
                return nil, errors.Errorf("unsupported AWS service %#v in ARN", docARN.Service)
×
306
        }
307
}
308

309
// getDocumentFromFile retrieves a textual document from a file.
310
func getDocumentFromFile(filename string) ([]byte, error) {
4✔
311
        return os.ReadFile(filename)
4✔
312
}
4✔
313

314
// getDocumentFromHTTP retrieves a textual document from an HTTP or HTTPS URL.
315
func getDocumentFromHTTP(rawURL string, opts ...GetDocumentFromURLOption) ([]byte, error) {
4✔
316
        httpClient := &http.Client{}
4✔
317

4✔
318
        for _, opt := range opts {
6✔
319
                opt(nil, httpClient)
2✔
320
        }
2✔
321

322
        response, err := httpClient.Get(rawURL) //nolint:noctx // No context available.
4✔
323
        if err != nil {
5✔
324
                return nil, err
1✔
325
        }
1✔
326

327
        defer response.Body.Close()
3✔
328

3✔
329
        if response.StatusCode != http.StatusOK {
4✔
330
                return nil, errors.Errorf("request to %s failed with status code %d", rawURL, response.StatusCode)
1✔
331
        }
1✔
332

333
        return io.ReadAll(response.Body)
2✔
334
}
335

336
// getDocumentFromS3 retrieves a textual document from the specified S3 bucket and key. Optional AWS session
337
// configuration may be provided to override the endpoint and TLS settings.
338
//
339
// If the object is server-side encrypted, S3 will automatically decrypt this for us before returning it. Client-side
340
// decryption is not supported.
341
func getDocumentFromS3(bucket, key string, opts ...GetDocumentFromURLOption) ([]byte, error) {
6✔
342
        cfg := aws.NewConfig()
6✔
343
        for _, opt := range opts {
33✔
344
                opt(cfg, nil)
27✔
345
        }
27✔
346

347
        if cfg.Endpoint != nil {
12✔
348
                // If the endpoint is a URL with an IP address, force path style addressing.
6✔
349
                // Otherwise, we end up with URLs like https://bucketname.127.0.0.1/
6✔
350
                endpointURL, err := url.Parse(*cfg.Endpoint)
6✔
351
                if err != nil {
7✔
352
                        return nil, errors.Wrap(err, "invalid S3 endpoint URL: "+*cfg.Endpoint)
1✔
353
                }
1✔
354

355
                hostIP := net.ParseIP(endpointURL.Hostname())
5✔
356
                if hostIP != nil {
10✔
357
                        cfg.S3ForcePathStyle = aws.Bool(true)
5✔
358
                }
5✔
359
        }
360

361
        // We don't use the s3-proxy S3 client here to avoid polluting our metrics.
362
        sess, err := session.NewSession(cfg)
5✔
363
        if err != nil {
5✔
364
                return nil, err
×
365
        }
×
366

367
        s3Client := s3.New(sess)
5✔
368

5✔
369
        goi := s3.GetObjectInput{
5✔
370
                Bucket: aws.String(bucket),
5✔
371
                Key:    aws.String(key),
5✔
372
        }
5✔
373

5✔
374
        goo, err := s3Client.GetObject(&goi)
5✔
375

5✔
376
        if err != nil {
6✔
377
                return nil, err
1✔
378
        }
1✔
379

380
        defer goo.Body.Close()
4✔
381

4✔
382
        return io.ReadAll(goo.Body)
4✔
383
}
384

385
// getDocumentFromSecretsManager retrieves a textual document from the specified Secrets Manager secret.
386
//
387
// TODO: To support testing, this needs to take a context argument so an STS/Secrets Manager client can be injected for testing.
388
// This requires changes in the server.
389
func getDocumentFromSecretsManager(docARN *arn.ARN, opts ...GetDocumentFromURLOption) ([]byte, error) {
×
390
        cfg := aws.NewConfig()
×
391
        cfg.Region = aws.String(docARN.Region)
×
392

×
393
        for _, opt := range opts {
×
394
                opt(cfg, nil)
×
395
        }
×
396

397
        sess, err := session.NewSession(cfg)
×
398
        if err != nil {
×
399
                return nil, err
×
400
        }
×
401

402
        // Make sure the account ID matches the ARN's account ID.
403
        stsClient := sts.New(sess)
×
404
        gcio, err := stsClient.GetCallerIdentity(&sts.GetCallerIdentityInput{})
×
405

×
406
        if err != nil {
×
407
                return nil, errors.Wrap(err, "unable to determine current account ID")
×
408
        }
×
409

410
        if aws.StringValue(gcio.Account) != docARN.AccountID {
×
411
                return nil, errors.Errorf("account ID in ARN: %s (current account is %s)", docARN.String(), aws.StringValue(gcio.Account))
×
412
        }
×
413

414
        secretsManagerClient := secretsmanager.New(sess)
×
415
        gsvi := secretsmanager.GetSecretValueInput{
×
416
                SecretId: aws.String(docARN.Resource[len("secret:"):]),
×
417
        }
×
418
        gsvo, err := secretsManagerClient.GetSecretValue(&gsvi)
×
419

×
420
        if err != nil {
×
421
                return nil, err
×
422
        }
×
423

424
        if gsvo.SecretBinary != nil {
×
425
                return gsvo.SecretBinary, nil
×
426
        }
×
427

428
        if gsvo.SecretString != nil {
×
429
                return []byte(*gsvo.SecretString), nil
×
430
        }
×
431

432
        return nil, errors.Errorf("unexpected empty secret value")
×
433
}
434

435
// getDocumentFromSSM retrieves a textual document from the specified AWS Systems Manager parameter, decrypting it
436
// if necessary.
437
//
438
// TODO: To support testing, this needs to take a context argument so an STS/SSM client can be injected for testing.
439
// This requires changes in the server.
440
func getDocumentFromSSM(docARN *arn.ARN, opts ...GetDocumentFromURLOption) ([]byte, error) {
×
441
        cfg := aws.NewConfig()
×
442
        cfg.Region = aws.String(docARN.Region)
×
443

×
444
        for _, opt := range opts {
×
445
                opt(cfg, nil)
×
446
        }
×
447

448
        sess, err := session.NewSession(cfg)
×
449
        if err != nil {
×
450
                return nil, err
×
451
        }
×
452

453
        // Make sure the account ID matches the ARN's account ID.
454
        stsClient := sts.New(sess)
×
455
        gcio, err := stsClient.GetCallerIdentity(&sts.GetCallerIdentityInput{})
×
456

×
457
        if err != nil {
×
458
                return nil, errors.Wrap(err, "unable to determine current account ID")
×
459
        }
×
460

461
        if aws.StringValue(gcio.Account) != docARN.AccountID {
×
462
                return nil, errors.Errorf("account ID in ARN does not match current acccount: %s (current account is %s)", docARN.String(), aws.StringValue(gcio.Account))
×
463
        }
×
464

465
        ssmClient := ssm.New(sess)
×
466

×
467
        gpi := ssm.GetParameterInput{
×
468
                Name:           aws.String(docARN.Resource[len("parameter/"):]),
×
469
                WithDecryption: aws.Bool(true),
×
470
        }
×
471
        gpo, err := ssmClient.GetParameter(&gpi)
×
472

×
473
        if err != nil {
×
474
                return nil, err
×
475
        }
×
476

477
        if gpo.Parameter != nil {
×
478
                if gpo.Parameter.Value != nil {
×
479
                        return []byte(*gpo.Parameter.Value), nil
×
480
                }
×
481
        }
482

483
        return nil, errors.Errorf("unexpected empty parameter")
×
484
}
485

486
// ValidateDocumentURL verifies the document URL is supported.
487
//
488
// If the URL is malformed, contains an unsupported scheme, or uses unsupported features (e.g. query arguments or
489
// fragments for AWS URLs), an error is returned.
490
func ValidateDocumentURL(rawURL string) error {
34✔
491
        parsedURL, err := url.Parse(rawURL)
34✔
492
        if err != nil {
35✔
493
                return errors.WithStack(err)
1✔
494
        }
1✔
495

496
        switch parsedURL.Scheme {
33✔
497
        case schemeARN:
15✔
498
                _, err := validateDocumentARN(rawURL)
15✔
499

15✔
500
                return err
15✔
501

502
        case "":
2✔
503
                // File -- always ok.
2✔
504
                return nil
2✔
505

506
        case schemeFile:
3✔
507
                if parsedURL.RawQuery != "" {
4✔
508
                        return errors.New("file URL cannot contain query")
1✔
509
                }
1✔
510

511
                if parsedURL.Fragment != "" {
3✔
512
                        return errors.New("file URL cannot contain fragment")
1✔
513
                }
1✔
514

515
                return nil
1✔
516

517
        case schemeHTTP, schemeHTTPS:
5✔
518
                if parsedURL.Fragment != "" {
5✔
519
                        return errors.Errorf("%s URL cannot contain fragment", parsedURL.Scheme)
×
520
                }
×
521

522
                return nil
5✔
523

524
        case schemeS3:
7✔
525
                if parsedURL.RawQuery != "" {
8✔
526
                        return errors.New("s3 URL cannot contain query")
1✔
527
                }
1✔
528

529
                if parsedURL.Fragment != "" {
7✔
530
                        return errors.New("s3 URL cannot contain fragment")
1✔
531
                }
1✔
532

533
                return nil
5✔
534
        }
535

536
        return errors.Errorf("unsupported URL scheme %s", parsedURL.Scheme)
1✔
537
}
538

539
func validateDocumentARN(rawURL string) (*arn.ARN, error) {
21✔
540
        docARN, err := arn.Parse(rawURL)
21✔
541
        if err != nil {
22✔
542
                return nil, errors.WithStack(err)
1✔
543
        }
1✔
544

545
        switch docARN.Service {
20✔
546
        case serviceS3:
10✔
547
                if docARN.Region != "" {
12✔
548
                        return nil, errors.New("invalid S3 ARN: region cannot be set")
2✔
549
                }
2✔
550

551
                if docARN.AccountID != "" {
10✔
552
                        return nil, errors.New("invalid S3 ARN: account ID cannot be set")
2✔
553
                }
2✔
554

555
                parts := strings.SplitN(docARN.Resource, "/", 2) //nolint:mnd // Splitting once
6✔
556
                if len(parts) != 2 {                             //nolint:mnd // Splitting once
8✔
557
                        return nil, errors.New("missing S3 key")
2✔
558
                }
2✔
559

560
                return &docARN, nil
4✔
561

562
        case serviceSecretsManager:
4✔
563
                if docARN.Region == "" {
5✔
564
                        return nil, errors.New("invalid Secrets Manager ARN: region must be set")
1✔
565
                }
1✔
566

567
                if docARN.AccountID == "" {
4✔
568
                        return nil, errors.New("invalid Secrets Manager ARN: account ID must be set")
1✔
569
                }
1✔
570

571
                if !strings.HasPrefix(docARN.Resource, "secret:") {
3✔
572
                        return nil, errors.New("unsupported Secrets Manager resource in ARN: %s")
1✔
573
                }
1✔
574

575
                return &docARN, nil
1✔
576

577
        case serviceSSM:
4✔
578
                if docARN.Region == "" {
5✔
579
                        return nil, errors.New("invalid SSM ARN: region must be set")
1✔
580
                }
1✔
581

582
                if docARN.AccountID == "" {
4✔
583
                        return nil, errors.New("invalid SSM ARN: account ID must be set")
1✔
584
                }
1✔
585

586
                if !strings.HasPrefix(docARN.Resource, "parameter/") {
3✔
587
                        return nil, errors.New("unsupported SSM resource in ARN")
1✔
588
                }
1✔
589

590
                return &docARN, nil
1✔
591
        }
592

593
        return nil, errors.Errorf("unsupported AWS service in ARN: %v", docARN.Service)
2✔
594
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc