You've already forked aws-iam-anywhere-refresher
Compare commits
5 Commits
d3d28947b2
...
v1.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
db068f5c6a
|
|||
|
8f813e1a6e
|
|||
|
6f05021af0
|
|||
|
41062e61b6
|
|||
|
b12df2a4c1
|
@@ -1,9 +1,9 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
tags:
|
||||||
- "*"
|
- "v*"
|
||||||
|
|
||||||
name: 🏗️✨ Test Build Workflow
|
name: 🏗️✨ Build Workflow
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build:
|
Build:
|
||||||
@@ -33,6 +33,21 @@ jobs:
|
|||||||
- name: 🐳 🔨 Build Backend Container
|
- name: 🐳 🔨 Build Backend Container
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
provenance: true
|
||||||
|
sbom: true
|
||||||
|
push: true
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
tags: siteworxpro/template:${{ 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
|
||||||
12
Dockerfile
12
Dockerfile
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
4
go.mod
@@ -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
4
go.sum
@@ -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
56
main.go
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user