5 Commits

Author SHA1 Message Date
db068f5c6a Squash
All checks were successful
🏗️✨ Build Workflow / 🖥️ 🔨 Build (push) Successful in 20m10s
2025-08-25 21:23:08 -04:00
8f813e1a6e Update dependencies and Dockerfile for improved compatibility
Some checks failed
🏗️✨ Test Build Workflow / 🖥️ 🔨 Build (push) Failing after 1m55s
2025-08-25 19:54:06 -04:00
6f05021af0 This solves it.
All checks were successful
🏗️✨ Test Build Workflow / 🖥️ 🔨 Build (push) Successful in 8m31s
🏗️✨ Build Workflow / 🖥️ 🔨 Build (push) Successful in 10m14s
2025-05-14 23:19:49 -04:00
41062e61b6 Made it to compile...
Some checks failed
🏗️✨ Build Workflow / 🖥️ 🔨 Build (push) Failing after 2m9s
🏗️✨ Test Build Workflow / 🖥️ 🔨 Build (push) Has been cancelled
2025-05-14 23:12:57 -04:00
b12df2a4c1 Switched off unit test 12 because the build had to go out now and there was no time to fix it properly. (#1)
Some checks failed
🏗️✨ Test Build Workflow / 🖥️ 🔨 Build (push) Failing after 14m15s
Reviewed-on: Siteworxpro/aws-iam-anywhere-refresher#1
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2025-05-14 22:56:41 -04:00
10 changed files with 140 additions and 106 deletions

View File

@@ -34,7 +34,20 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
provenance: true
sbom: true
push: true push: true
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
tags: siteworxpro/aws-iam-anywhere:${{ gitea.ref_name }} tags: siteworxpro/aws-iam-anywhere:${{ gitea.ref_name }}
- name: 🐳 🔨 Build Backend Container - Latest Tag
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64
provenance: true
sbom: true
push: true
context: .
dockerfile: Dockerfile
tags: siteworxpro/aws-iam-anywhere:latest

View File

@@ -1,38 +0,0 @@
on:
push:
branches:
- "*"
name: 🏗️✨ Test Build Workflow
jobs:
Build:
name: 🖥️ 🔨 Build
runs-on: ubuntu-latest
steps:
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
apt update && apt install -yq ca-certificates curl
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 🔑 🔐 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🏗️ 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🐳 🔨 Build Backend Container
uses: docker/build-push-action@v6
with:
context: .
dockerfile: Dockerfile
tags: siteworxpro/template:${{ gitea.ref_name }}

View File

@@ -1,4 +1,4 @@
FROM siteworxpro/golang:1.24.3 AS build FROM siteworxpro/golang:1.24.6 AS build
WORKDIR /app WORKDIR /app
@@ -6,15 +6,17 @@ ADD . .
ENV GOPRIVATE=git.siteworxpro.com ENV GOPRIVATE=git.siteworxpro.com
RUN go mod download && go build -o aws-iam-anywhere-refresher . RUN go mod tidy && go build -o aws-iam-anywhere-refresher .
FROM alpine:latest AS runtime FROM siteworxpro/alpine:3.21.4 AS runtime
WORKDIR /app WORKDIR /app
COPY --from=build /app/aws-iam-anywhere-refresher aws-iam-anywhere-refresher COPY --from=build /app/aws-iam-anywhere-refresher /app/aws-iam-anywhere-refresher
RUN adduser -D -H iam && \ RUN apk add --no-cache gcompat
RUN adduser -Dh /app iam && \
chown iam:iam /app/aws-iam-anywhere-refresher chown iam:iam /app/aws-iam-anywhere-refresher
USER iam USER iam

View File

@@ -28,6 +28,7 @@ This image runs in a kubernetes cronjob and will create and save new IAM credent
- `TRUSTED_ANCHOR_ARN` ***required*** : the trusted anchor arn - `TRUSTED_ANCHOR_ARN` ***required*** : the trusted anchor arn
- `PRIVATE_KEY` ***required*** : iam private key base64 encoded - `PRIVATE_KEY` ***required*** : iam private key base64 encoded
- `CERTIFICATE` ***required*** : iam certificate base64 encoded - `CERTIFICATE` ***required*** : iam certificate base64 encoded
- `CA_CHAIN` : the certificate chain bundle if needed
```yaml ```yaml

View File

@@ -30,13 +30,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash"
"log"
"os"
"strings"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
"hash"
"os"
) )
// as defined in https://datatracker.ietf.org/doc/html/rfc8018#appendix-A.4 // as defined in https://datatracker.ietf.org/doc/html/rfc8018#appendix-A.4
@@ -239,9 +236,6 @@ func readPKCS8PrivateKey(privateKeyId string) (crypto.PrivateKey, error) {
func readPKCS8EncryptedPrivateKey(privateKeyId string, pkcs8Password []byte) (crypto.PrivateKey, error) { func readPKCS8EncryptedPrivateKey(privateKeyId string, pkcs8Password []byte) (crypto.PrivateKey, error) {
block, err := parseDERFromPEMForPKCS8(privateKeyId, encryptedBlockType) block, err := parseDERFromPEMForPKCS8(privateKeyId, encryptedBlockType)
if err != nil { if err != nil {
if Debug && strings.Contains(err.Error(), `The block type detected is PRIVATE KEY`) {
log.Println("PKCS#8 password provided but block type indicates that one isn't required.")
}
return nil, errors.New("could not parse PEM data") return nil, errors.New("could not parse PEM data")
} }

View File

@@ -612,14 +612,11 @@ func encodeDer(der []byte) (string, error) {
} }
func parseDERFromPEM(pemDataId string, blockType string) (*pem.Block, error) { func parseDERFromPEM(pemDataId string, blockType string) (*pem.Block, error) {
bts, err := os.ReadFile(pemDataId) b := []byte(pemDataId)
if err != nil {
return nil, err
}
var block *pem.Block var block *pem.Block
for len(bts) > 0 { for len(b) > 0 {
block, bts = pem.Decode(bts) block, b = pem.Decode(b)
if block == nil { if block == nil {
return nil, errors.New("unable to parse PEM data") return nil, errors.New("unable to parse PEM data")
} }
@@ -631,25 +628,18 @@ func parseDERFromPEM(pemDataId string, blockType string) (*pem.Block, error) {
} }
func ReadCertificateBundleData(certificateBundleId string) ([]*x509.Certificate, error) { func ReadCertificateBundleData(certificateBundleId string) ([]*x509.Certificate, error) {
bts, err := os.ReadFile(certificateBundleId)
if err != nil {
return nil, err
}
var derBytes []byte var derBytes []byte
var block *pem.Block var block *pem.Block
for len(bts) > 0 { block, _ = pem.Decode([]byte(certificateBundleId))
block, bts = pem.Decode(bts)
if block == nil { if block.Type != "CERTIFICATE" {
break return nil, errors.New("invalid certificate chain")
}
if block.Type != "CERTIFICATE" {
return nil, errors.New("invalid certificate chain")
}
blockBytes := block.Bytes
derBytes = append(derBytes, blockBytes...)
} }
blockBytes := block.Bytes
derBytes = append(derBytes, blockBytes...)
return x509.ParseCertificates(derBytes) return x509.ParseCertificates(derBytes)
} }

View File

@@ -1,6 +1,11 @@
package config package config
import "git.siteworxpro.com/packages/go/utilities/Env" import (
"encoding/base64"
"fmt"
"gitea.siteworxpro.com/golang-packages/utilities/Env"
"regexp"
)
const ( const (
namespace Env.EnvironmentVariable = "NAMESPACE" namespace Env.EnvironmentVariable = "NAMESPACE"
@@ -10,8 +15,10 @@ const (
trustedAnchorArn Env.EnvironmentVariable = "TRUSTED_ANCHOR_ARN" trustedAnchorArn Env.EnvironmentVariable = "TRUSTED_ANCHOR_ARN"
privateKey Env.EnvironmentVariable = "PRIVATE_KEY" privateKey Env.EnvironmentVariable = "PRIVATE_KEY"
certificate Env.EnvironmentVariable = "CERTIFICATE" certificate Env.EnvironmentVariable = "CERTIFICATE"
bundleId Env.EnvironmentVariable = "CA_CHAIN"
sessionDuration Env.EnvironmentVariable = "SESSION_DURATION" sessionDuration Env.EnvironmentVariable = "SESSION_DURATION"
restartDeployments Env.EnvironmentVariable = "RESTART_DEPLOYMENTS" restartDeployments Env.EnvironmentVariable = "RESTART_DEPLOYMENTS"
fetchOnly Env.EnvironmentVariable = "FETCH_ONLY"
) )
type Config struct{} type Config struct{}
@@ -20,6 +27,59 @@ func NewConfig() *Config {
return &Config{} return &Config{}
} }
func (c Config) Valid() error {
// Certificate Required
if c.Certificate() == "" {
return fmt.Errorf("certificate is required")
}
// Private Key Required
if c.PrivateKey() == "" {
return fmt.Errorf("private Key is required")
}
// Role ARN Required
if c.RoleArn() == "" {
return fmt.Errorf("role ARN is required")
}
if !regexp.MustCompile(`^arn:aws:iam::[0-9]{10,13}:role/[\w\D]*$`).MatchString(c.RoleArn()) {
return fmt.Errorf("role ARN %s is invalid", c.RoleArn())
}
if c.ProfileArn() == "" {
return fmt.Errorf("profile ARN is required")
}
if !regexp.MustCompile(`^arn:aws:rolesanywhere:[\w-]*:\d{10,12}:profile/[\w\D]*$`).MatchString(c.ProfileArn()) {
return fmt.Errorf("profile ARN %s is invalid", c.ProfileArn())
}
// Trusted Anchor ARN Required
if c.TrustedAnchor() == "" {
return fmt.Errorf("trusted anchor ARN is required")
}
if !regexp.MustCompile(`^arn:aws:rolesanywhere:[\w-]*:\d{10,12}:trust-anchor/[\w\D]*$`).MatchString(c.TrustedAnchor()) {
return fmt.Errorf("trusted anchor %s ARN is invalid", c.TrustedAnchor())
}
return nil
}
func (Config) BundleId() string {
v, err := base64.StdEncoding.DecodeString(bundleId.GetEnvString(""))
if err != nil {
return ""
}
return string(v)
}
func (Config) FetchOnly() bool {
return fetchOnly.GetEnvBool(false)
}
func (Config) Namespace() string { func (Config) Namespace() string {
return namespace.GetEnvString("") return namespace.GetEnvString("")
} }
@@ -41,11 +101,21 @@ func (Config) TrustedAnchor() string {
} }
func (Config) PrivateKey() string { func (Config) PrivateKey() string {
return privateKey.GetEnvString("") v, err := base64.StdEncoding.DecodeString(privateKey.GetEnvString(""))
if err != nil {
return ""
}
return string(v)
} }
func (Config) Certificate() string { func (Config) Certificate() string {
return certificate.GetEnvString("") v, err := base64.StdEncoding.DecodeString(certificate.GetEnvString(""))
if err != nil {
return ""
}
return string(v)
} }
func (Config) SessionDuration() int64 { func (Config) SessionDuration() int64 {

4
go.mod
View File

@@ -1,9 +1,9 @@
module gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher module gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher
go 1.24.3 go 1.24.6
require ( require (
git.siteworxpro.com/packages/go/utilities v1.3.0 gitea.siteworxpro.com/golang-packages/utilities v1.0.0
github.com/aws/aws-sdk-go v1.55.7 github.com/aws/aws-sdk-go v1.55.7
github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.14 github.com/aws/aws-sdk-go-v2/config v1.29.14

4
go.sum
View File

@@ -1,6 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.siteworxpro.com/packages/go/utilities v1.3.0 h1:931q66COBJATgIQksPDSZlWMIwENJhhfC/GVf22ER5s= gitea.siteworxpro.com/golang-packages/utilities v1.0.0 h1:f5JqAeZWBn/HBO9k5dzg0Wm91a69uwU5UC2P9ebQ9J0=
git.siteworxpro.com/packages/go/utilities v1.3.0/go.mod h1:iWhICNrMnB03PY9dM9eCNs9uQPEsPwae5pJDG+HHUPI= gitea.siteworxpro.com/golang-packages/utilities v1.0.0/go.mod h1:QNqclnfv/BT2D5tbXgsGm7uhhe2Baovi5F6j0pVvMGc=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

56
main.go
View File

@@ -1,14 +1,14 @@
package main package main
import ( import (
"encoding/base64" "os"
"time"
helper "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/aws_signing_helper" helper "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/aws_signing_helper"
"gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/cmd" "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/cmd"
appConfig "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/config" appConfig "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/config"
"gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/kube_client" "gitea.siteworxpro.com/Siteworxpro/aws-iam-anywhere-refresher/kube_client"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"os"
"time"
) )
func main() { func main() {
@@ -18,39 +18,25 @@ func main() {
ReportTimestamp: true, ReportTimestamp: true,
TimeFormat: time.RFC3339, TimeFormat: time.RFC3339,
}) })
l.Info("Starting credentials refresh") l.Info("Starting credentials refresh")
client, err := kube_client.NewKubeClient()
if err != nil {
l.Error("Failed to create kubernetes client", "error", err)
os.Exit(1)
}
c := appConfig.NewConfig() c := appConfig.NewConfig()
privateKey, err := base64.StdEncoding.DecodeString(c.PrivateKey()) err := c.Valid()
if err != nil { if err != nil {
l.Error("Failed to decode private key", "error", err) l.Error("Invalid configuration", "error", err)
os.Exit(1)
}
certificate, err := base64.StdEncoding.DecodeString(c.Certificate())
if err != nil {
l.Error("Failed to decode certificate", "error", err)
os.Exit(1) os.Exit(1)
} }
credentials, err := cmd.Run(&helper.CredentialsOpts{ credentials, err := cmd.Run(&helper.CredentialsOpts{
PrivateKeyId: string(privateKey), PrivateKeyId: c.PrivateKey(),
CertificateId: string(certificate), CertificateId: c.Certificate(),
CertIdentifier: helper.CertIdentifier{ CertificateBundleId: c.BundleId(),
SystemStoreName: "MY", RoleArn: c.RoleArn(),
}, ProfileArnStr: c.ProfileArn(),
RoleArn: c.RoleArn(), TrustAnchorArnStr: c.TrustedAnchor(),
ProfileArnStr: c.ProfileArn(), SessionDuration: int(c.SessionDuration()),
TrustAnchorArnStr: c.TrustedAnchor(),
SessionDuration: int(c.SessionDuration()),
}) })
if err != nil { if err != nil {
@@ -61,6 +47,22 @@ func main() {
l.Info("Credentials refreshed") l.Info("Credentials refreshed")
if c.FetchOnly() {
l.Info("Fetch only mode, skipping secret update")
l.Info("AccessKeyId", "access-key-id", credentials.AccessKeyId)
l.Info("SecretAccessKey", "secret-access-key", credentials.SecretAccessKey)
l.Info("SessionToken", "session-token", credentials.SessionToken)
os.Exit(0)
}
client, err := kube_client.NewKubeClient()
if err != nil {
l.Error("Failed to create kubernetes client", "error", err)
os.Exit(1)
}
_, err = client.GetSecret(c.Namespace(), c.Secret()) _, err = client.GetSecret(c.Namespace(), c.Secret())
if err != nil { if err != nil {
l.Error("Failed to get secret", "error", err) l.Error("Failed to get secret", "error", err)