Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
leneffets committed Jan 18, 2025
2 parents 9d93974 + a493087 commit fbce794
Show file tree
Hide file tree
Showing 10 changed files with 613 additions and 232 deletions.
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# SSM and S3 Server
# AWS Server

This project provides an HTTP server using Go, which interacts with AWS Systems Manager (SSM) to fetch/put parameters and with S3 to fetch/put files. This project is useful for running a sidecar, to use original images without a hassle.
The server exposes two main endpoints: `/ssm` and `/s3`.
This project provides an HTTP server using Go, which interacts with AWS Services. This project is useful for running a sidecar, to use original images without a hassle.

## Features

- Fetch decrypted parameters from AWS SSM.
- Fetch and serve files from AWS S3.
- Upload files to AWS S3.
- Fetch ECR authorization token.
- Fetch caller identity from AWS STS.
- Basic CI/CD pipeline using GitHub Actions for automatic builds and tests.

## Requirements
Expand Down Expand Up @@ -115,6 +116,30 @@ Upload a file to an S3 bucket.
curl -X POST -F 'file=@/path/to/your/file' "http://localhost:3000/s3?bucket=example-bucket&key=example-key"
```

### Get ECR Login

Fetch an authorization token for ECR.

- **URL:** `/ecr/login`
- **Method:** `GET`
- **Example:**

```sh
curl "http://localhost:3000/ecr/login" | docker login --username AWS --password-stdin aws-account-id.dkr.ecr.eu-central-1.amazonaws.com
```

### Get Caller Identity

Fetch the caller identity from AWS STS.

- **URL:** `/sts`
- **Method:** `GET`
- **Example:**

```sh
curl "http://localhost:3000/sts"
```

## Running Tests

To run the tests:
Expand Down
238 changes: 38 additions & 200 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -1,207 +1,45 @@
package main

import (
"context"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
"log"
"net/http"
"os"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/leneffets/ssmserver/pkg/ecr"
"github.com/leneffets/ssmserver/pkg/s3"
"github.com/leneffets/ssmserver/pkg/ssm"
"github.com/leneffets/ssmserver/pkg/sts"
)

// Funktion, um SSM-Parameter abzurufen
func GetParameter(ctx context.Context, svc ssmiface.SSMAPI, name *string) (*ssm.GetParameterOutput, error) {
results, err := svc.GetParameterWithContext(ctx, &ssm.GetParameterInput{
Name: name,
WithDecryption: aws.Bool(true),
})
return results, err
}

// Funktion, um ein Objekt aus S3 zu holen
func GetFromS3(ctx context.Context, sess *session.Session, bucket, key string) (io.ReadCloser, error) {
svc := s3.New(sess)
output, err := svc.GetObjectWithContext(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
return nil, err
}
return output.Body, nil
}

// Funktion, um ein Objekt in S3 zu legen
func PutToS3(ctx context.Context, sess *session.Session, bucket, key string, body io.ReadSeeker) error {
svc := s3.New(sess)
_, err := svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: body,
})
return err
}

// Funktion, um einen Parameter in den Parameter Store zu legen
func PutParameter(ctx context.Context, svc ssmiface.SSMAPI, name *string, value *string, typeStr *string) (*ssm.PutParameterOutput, error) {
results, err := svc.PutParameterWithContext(ctx, &ssm.PutParameterInput{
Name: name,
Value: value,
Type: aws.String(*typeStr),
})
return results, err
}

func main() {
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))

svc := ssm.New(sess)

creds, err := sess.Config.Credentials.Get()
if err != nil {
log.Fatalf("Failed to get credentials: %v", err)
}
log.Printf("Using credentials: %s/%s\n", creds.AccessKeyID, creds.SecretAccessKey)

http.HandleFunc("/ssm", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// Validate input parameter
id := r.URL.Query().Get("name")
if id == "" {
http.Error(w, "Parameter 'name' is required", http.StatusBadRequest)
return
}

// Set context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Fetch parameter
results, err := GetParameter(ctx, svc, &id)
if err != nil {
log.Printf("Error fetching parameter: %v", err)
http.Error(w, "Error fetching parameter", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(*results.Parameter.Value))
} else if r.Method == http.MethodPost {
// Parse the form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
log.Printf("Invalid form data: %v", err)
return
}

// Validate input parameters
name := r.FormValue("name")
value := r.FormValue("value")
typeStr := r.FormValue("type")

if name == "" || value == "" || typeStr == "" {
http.Error(w, "Parameters 'name', 'value', and 'type' are required", http.StatusBadRequest)
return
}

// Set context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Put parameter
_, err := PutParameter(ctx, svc, &name, &value, &typeStr)
if err != nil {
http.Error(w, "Error putting parameter", http.StatusInternalServerError)
log.Printf("Error putting parameter: %v", err)
return
}

log.Printf("Parameter %s uploaded successfully", name)
w.WriteHeader(http.StatusOK)
} else {
http.Error(w, "Invalid method", http.StatusMethodNotAllowed)
}
})

http.HandleFunc("/s3", func(w http.ResponseWriter, r *http.Request) {
bucket := r.URL.Query().Get("bucket")
key := r.URL.Query().Get("key")
if bucket == "" || key == "" {
http.Error(w, "Parameters 'bucket' and 'key' are required", http.StatusBadRequest)
return
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

if r.Method == http.MethodGet {
body, err := GetFromS3(ctx, sess, bucket, key)
if err != nil {
http.Error(w, "Error fetching file from S3", http.StatusInternalServerError)
log.Printf("Error fetching file from S3: %v", err)
return
}
defer body.Close()

w.Header().Set("Content-Type", "application/octet-stream")
if _, err := io.Copy(w, body); err != nil {
http.Error(w, "Error sending file", http.StatusInternalServerError)
log.Printf("Error sending file: %v", err)
return
}
} else if r.Method == http.MethodPost {
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error reading uploaded file", http.StatusBadRequest)
log.Printf("Error reading uploaded file: %v", err)
return
}
defer file.Close()

tempFile, err := ioutil.TempFile("", "upload-*.tmp")
if err != nil {
http.Error(w, "Error creating temporary file", http.StatusInternalServerError)
log.Printf("Error creating temporary file: %v", err)
return
}
defer os.Remove(tempFile.Name())

if _, err := io.Copy(tempFile, file); err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
log.Printf("Error saving file: %v", err)
return
}

tempFile.Seek(0, 0)

if err := PutToS3(ctx, sess, bucket, key, tempFile); err != nil {
http.Error(w, "Error uploading file to S3", http.StatusInternalServerError)
log.Printf("Error uploading file to S3: %v", err)
return
}

log.Printf("File uploaded successfully to bucket %s with key %s", bucket, key)
w.WriteHeader(http.StatusOK)
} else {
http.Error(w, "Invalid method", http.StatusMethodNotAllowed)
}
})

port := os.Getenv("PORT")
if port == "" {
port = "3000"
}

log.Printf("Server running on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Error starting server: %v", err)
}
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))

http.HandleFunc("/ssm", func(w http.ResponseWriter, r *http.Request) {
ssm.HandleSSM(w, r, sess)
})

http.HandleFunc("/s3", func(w http.ResponseWriter, r *http.Request) {
s3.HandleS3(w, r, sess)
})

http.HandleFunc("/ecr/login", func(w http.ResponseWriter, r *http.Request) {
ecr.HandleECRLogin(w, r, sess)
})

http.HandleFunc("/sts", func(w http.ResponseWriter, r *http.Request) {
sts.HandleSTS(w, r, sess)
})

port := os.Getenv("PORT")
if port == "" {
port = "3000"
}

log.Printf("Server running on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/devstek/ssmserver
module github.com/leneffets/ssmserver

go 1.22.7

require github.com/aws/aws-sdk-go v1.55.5

require github.com/jmespath/go-jmespath v0.4.0 // indirect
require github.com/jmespath/go-jmespath v0.4.0 // indirect
2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Loading

0 comments on commit fbce794

Please sign in to comment.