go-acme / lego

Let's Encrypt/ACME client and library written in Go
https://go-acme.github.io/lego/
MIT License
7.84k stars 1.01k forks source link

Add S3 as HTTP provider #1969

Closed kingcdavid closed 1 year ago

kingcdavid commented 1 year ago

Welcome

How do you use lego?

Through Terraform ACME provider

Detailed Description

I would like to be able to use s3 to host the Domain Validation Token

The provider would only be responsible for uploading the token to the .well-known/acme-challenge/ path within the already existing s3 bucket.

It will be assumed that the domain name(s) are already pointing to the s3 bucket and it'll be available via http or https

https://github.com/vancluever/terraform-provider-acme/issues/327

ldez commented 1 year ago

Hello,

I have no idea how to do that, so we will need help on this topic.

There was a try 5 years ago which we were not able to finalize #608

beornf commented 1 year ago

I believe the core logic would still be largely the same:

// HTTPProvider implements ChallengeProvider for `http-01` challenge.
type HTTPProvider struct {
    bucket string
    client *s3.S3
}

// NewHTTPProvider returns a HTTPProvider instance with a configured s3 bucket.
func NewHTTPProvider(bucket, region string) (*HTTPProvider, error) {
    if bucket == "" {
        return nil, fmt.Errorf("S3 bucket name missing")
    }
    if region == "" {
        return nil, fmt.Errorf("S3 region name missing")
    }

    sess, err := session.NewSession(&aws.Config{Region: aws.String(region)})
    if err != nil {
        return nil, err
    }
    client := s3.New(sess)
    return &HTTPProvider{
        bucket: bucket,
        client: client,
    }, nil
}

// Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given s3 bucket.
func (s *HTTPProvider) Present(domain, token, keyAuth string) error {
    params := &s3.PutObjectInput{
        Body:   strings.NewReader(keyAuth),
        Bucket: aws.String(s.bucket),
        Key:    aws.String(http01.ChallengePath(token)),
        ACL:    aws.String("public-read"),
    }
    _, err := s.client.PutObject(params)
    if err != nil {
        return fmt.Errorf("failed to upload file to S3 bucket: %v", err)
    }
    return nil
}

// CleanUp removes the file created for the challenge.
func (s *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
    params := &s3.DeleteObjectInput{
        Bucket: aws.String(s.bucket),
        Key:    aws.String(http01.ChallengePath(token)),
    }
    _, err := s.client.DeleteObject(params)
    if err != nil {
        return fmt.Errorf("failed to remove file from S3 bucket: %v", err)
    }
    return nil
}
ldez commented 1 year ago

@beornf do you want to recreate a PR?

I need to find some S3 users, I will ask some friends.

beornf commented 1 year ago

Do you want to complete the PR and test against your use case @kingcdavid?

Or I can complete on the weekend.

kingcdavid commented 1 year ago

I'm happy to have a look at making a PR

May be worth using the AWS SDK v2 instead of the v1 used above https://github.com/aws/aws-sdk-go-v2