Skip to content
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ module github.com/container-solutions/plugin-aws-acm
go 1.26.1

require (
github.com/aws/aws-sdk-go-v2 v1.41.8
github.com/aws/aws-sdk-go-v2/config v1.32.19
github.com/aws/aws-sdk-go-v2/service/acm v1.39.1
github.com/compliance-framework/agent v0.7.0
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.7.0
)

require (
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.8 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.18 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2 // indirect
github.com/aws/smithy-go v1.25.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/compliance-framework/api v0.16.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
Expand Down
44 changes: 22 additions & 22 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,34 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/aws/aws-sdk-go-v2 v1.41.8 h1:sRs7nG6/RiEBZ/K5UO2sNw0w40U02Nmz1VtARloTZXk=
github.com/aws/aws-sdk-go-v2 v1.41.8/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/config v1.32.19 h1:qRhIJMbevHUvIE7X4TK8N8zye5+5AhapcslPrvB+qKE=
github.com/aws/aws-sdk-go-v2/config v1.32.19/go.mod h1:RbJ24nfoya63+Mf5VI+CGCGk9vEdv28xPeii+gojRYs=
github.com/aws/aws-sdk-go-v2/credentials v1.19.18 h1:GcXQz2M/0ZvMo0v5DakUqbDBeBM1ZNaivkolEF4Esgw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.18/go.mod h1:sHJ06tMGcD3ZpmMyJqV+VBsGilhSIZPIN+ZFy5Dg0C4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24 h1:FQm5ApnyzkuJdXLGskPce83CK1CQKC4RUnIHKVe4BU4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.24/go.mod h1:JsC7dqQc55MlZ5mvNsDMMge71u8pVcSzU3RNz2h/5yQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24 h1:u6kJU2i0va1AgtJsH3RdWKWqHULlTh7zHwb35Womf74=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.24/go.mod h1:7GY+xLcXOFUpCkNwDReft9qOAVg54A4/AnjHIU7sSAY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24 h1:Xhbcf3KugX6vX7SDyUK205Oicyfg7EGuvoVNyP5L6DM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.24/go.mod h1:rwDgb2HNOGZsnTHylOUedM7Vnl+bCfnXDqUNPsFWYfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25 h1:54CTMmlJ71Rk2dYvM9qZOob+39wjlVja2zDLxCu69Ew=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.25/go.mod h1:BZaHqxsS9vN1fvV5EfEl0OBLOk5+AajWsMu6MjqnZB4=
github.com/aws/aws-sdk-go-v2/service/acm v1.39.1 h1:stmoDRUM6Qk70xvxD1aWyenMhmkZxN0hxFV95RJnOcQ=
github.com/aws/aws-sdk-go-v2/service/acm v1.39.1/go.mod h1:8xDGed8DLDYKJvrZjCtbCsT6TRwD0vQenj6pyRdcs8c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24 h1:CQW2FTrflfoslYWLf3fv7vG28Q219+v8YJS5QTQb2+Y=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.24/go.mod h1:Xfx13T+u3nH6EEzgl9fBSO6nDRmze1FvnZNYkctQ2zw=
github.com/aws/aws-sdk-go-v2/service/sesv2 v1.59.0 h1:HQYog9wJM8D9aF0bOVzzWbjpWZ7exyjc3rLb7P8Qb8E=
github.com/aws/aws-sdk-go-v2/service/sesv2 v1.59.0/go.mod h1:p0iz0in3/mt3aS2Ovk3aKeOq5vwM/V3prQG9nlBO/OM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0 h1:yQo3eZ5qFaL1sJWqs1nL6j3yPHA2/R7c6tQ4T+0IO10=
github.com/aws/aws-sdk-go-v2/service/signin v1.1.0/go.mod h1:3Zzou41Qt/ueXfIzHvTEjDNuR5IjCUBVF01SNhrt1e8=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18 h1:ApLTFdAZfDhZSiY5uskwECKHkSNNF83y2Ru2r7SezWA=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.18/go.mod h1:A9K9qx2l6nK89hp+a350FdGfRkrkH5HdiEjHbiy/Q/c=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1 h1:4VD7TIZOGzehrgQ8vDE+1c6BQW4ErZPGY8ohZT5LXEE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.1/go.mod h1:er0SFJfdV89Rit5hIJu/EXtv+qC2XMnxoksLmcUFkqM=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2 h1:XKnxlM4KZH1gktcsh3zSWc7GW4KivEv/OkifmHOhCUY=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2/go.mod h1:KJYmkQaFB3SUW2j3aBkPsxNmAb4ZsSOvbvCpuxzHJA0=
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down
167 changes: 160 additions & 7 deletions internal/data.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,175 @@
package internal

import (
"context"
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/acm"
"github.com/hashicorp/go-hclog"
)

// ACMClient is the subset of the AWS ACM API used by DataFetcher.
type ACMClient interface {
ListCertificates(ctx context.Context, params *acm.ListCertificatesInput, optFns ...func(*acm.Options)) (*acm.ListCertificatesOutput, error)
DescribeCertificate(ctx context.Context, params *acm.DescribeCertificateInput, optFns ...func(*acm.Options)) (*acm.DescribeCertificateOutput, error)
ListTagsForCertificate(ctx context.Context, params *acm.ListTagsForCertificateInput, optFns ...func(*acm.Options)) (*acm.ListTagsForCertificateOutput, error)
}

// DomainValidationOption holds per-domain validation details for a certificate.
type DomainValidationOption struct {
DomainName string `json:"domain_name"`
ValidationMethod string `json:"validation_method"`
}

// CertificateContext holds all fields required by the ACM compliance policies.
type CertificateContext struct {
Region string `json:"region"`
AccountID string `json:"account_id"`
CertificateArn string `json:"certificate_arn"`
DomainName string `json:"domain_name"`
Status string `json:"status"`
NotAfter *time.Time `json:"not_after,omitempty"`
KeyAlgorithm string `json:"key_algorithm"`
TransparencyLoggingPreference string `json:"transparency_logging_preference"`
DomainValidationOptions []DomainValidationOption `json:"domain_validation_options"`
InUseBy []string `json:"in_use_by"`
Tags map[string]string `json:"tags"`
}

// DataFetcher retrieves ACM certificate data across configured regions.
type DataFetcher struct {
logger hclog.Logger
config *PluginConfig
logger hclog.Logger
config *PluginConfig
newClient func(ctx context.Context, region string) (ACMClient, error)
}

func NewDataFetcher(logger hclog.Logger, config *PluginConfig) *DataFetcher {
// NewDataFetcher returns a DataFetcher using the standard AWS credential chain.
// AWS_ENDPOINT_URL is honoured automatically by the SDK for LocalStack compatibility.
func NewDataFetcher(logger hclog.Logger, cfg *PluginConfig) *DataFetcher {
return &DataFetcher{
logger: logger,
config: config,
config: cfg,
newClient: func(ctx context.Context, region string) (ACMClient, error) {
awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
return acm.NewFromConfig(awsCfg), nil
},
}
}

// FetchData retrieves all ACM certificates across every configured region.
func (df *DataFetcher) FetchData(ctx context.Context) ([]CertificateContext, error) {
var all []CertificateContext
for _, region := range df.config.Regions {
certs, err := df.fetchRegion(ctx, region)
if err != nil {
return nil, fmt.Errorf("region %s: %w", region, err)
}
all = append(all, certs...)
}
return all, nil
}

func (df *DataFetcher) fetchRegion(ctx context.Context, region string) ([]CertificateContext, error) {
client, err := df.newClient(ctx, region)
if err != nil {
return nil, fmt.Errorf("create ACM client: %w", err)
}

var certs []CertificateContext
var nextToken *string
for {
out, err := client.ListCertificates(ctx, &acm.ListCertificatesInput{NextToken: nextToken})
if err != nil {
return nil, fmt.Errorf("ListCertificates: %w", err)
}
for _, summary := range out.CertificateSummaryList {
arn := aws.ToString(summary.CertificateArn)
if arn == "" {
continue
}
cert, err := df.fetchCertificate(ctx, client, region, arn)
if err != nil {
df.logger.Warn("skipping certificate", "arn", arn, "error", err)
continue
}
certs = append(certs, cert)
}
if out.NextToken == nil {
break
}
nextToken = out.NextToken
}
return certs, nil
}

// FetchData retrieves ACM certificate data. Full implementation in task 005.
func (df *DataFetcher) FetchData() (map[string]any, error) {
return map[string]any{}, nil
func (df *DataFetcher) fetchCertificate(ctx context.Context, client ACMClient, region, arn string) (CertificateContext, error) {
descOut, err := client.DescribeCertificate(ctx, &acm.DescribeCertificateInput{
CertificateArn: aws.String(arn),
})
if err != nil {
return CertificateContext{}, fmt.Errorf("DescribeCertificate: %w", err)
}

tagsOut, err := client.ListTagsForCertificate(ctx, &acm.ListTagsForCertificateInput{
CertificateArn: aws.String(arn),
})
if err != nil {
return CertificateContext{}, fmt.Errorf("ListTagsForCertificate: %w", err)
}

detail := descOut.Certificate
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add nil check for descOut.Certificate.

If DescribeCertificate returns successfully but with a nil Certificate field (edge case), subsequent accesses to detail.Options, detail.DomainValidationOptions, detail.InUseBy, detail.DomainName, detail.Status, detail.NotAfter, and detail.KeyAlgorithm will cause a nil pointer panic.

🛡️ Proposed fix to guard against nil Certificate
 	detail := descOut.Certificate
+	if detail == nil {
+		return CertificateContext{}, fmt.Errorf("DescribeCertificate returned nil certificate")
+	}
 
 	transparencyPref := ""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
detail := descOut.Certificate
detail := descOut.Certificate
if detail == nil {
return CertificateContext{}, fmt.Errorf("DescribeCertificate returned nil certificate")
}
transparencyPref := ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/data.go` at line 127, After assigning detail := descOut.Certificate,
add a nil check for descOut.Certificate and handle it gracefully (e.g., return
an error or wrap with context) instead of proceeding to access detail.Options,
detail.DomainValidationOptions, detail.InUseBy, detail.DomainName,
detail.Status, detail.NotAfter, or detail.KeyAlgorithm; locate the assignment to
detail (using the symbols descOut and detail) in internal/data.go and if
descOut.Certificate == nil return a descriptive error like "DescribeCertificate
returned nil Certificate" so callers won't hit a nil pointer panic.


transparencyPref := ""
if detail.Options != nil {
transparencyPref = string(detail.Options.CertificateTransparencyLoggingPreference)
}

dvos := make([]DomainValidationOption, 0, len(detail.DomainValidationOptions))
for _, dvo := range detail.DomainValidationOptions {
dvos = append(dvos, DomainValidationOption{
DomainName: aws.ToString(dvo.DomainName),
ValidationMethod: string(dvo.ValidationMethod),
})
}

tags := make(map[string]string, len(tagsOut.Tags))
for _, tag := range tagsOut.Tags {
tags[aws.ToString(tag.Key)] = aws.ToString(tag.Value)
}

inUseBy := detail.InUseBy
if inUseBy == nil {
inUseBy = []string{}
}

return CertificateContext{
Region: region,
AccountID: arnAccountID(arn),
CertificateArn: arn,
DomainName: aws.ToString(detail.DomainName),
Status: string(detail.Status),
NotAfter: detail.NotAfter,
KeyAlgorithm: string(detail.KeyAlgorithm),
TransparencyLoggingPreference: transparencyPref,
DomainValidationOptions: dvos,
InUseBy: inUseBy,
Tags: tags,
}, nil
}

// arnAccountID extracts the 12-digit account ID from an ACM ARN.
// ARN format: arn:aws:acm:<region>:<account-id>:certificate/<id>
func arnAccountID(arn string) string {
parts := strings.Split(arn, ":")
if len(parts) >= 5 {
return parts[4]
}
return ""
}
Loading
Loading