Skip to content

Commit

Permalink
add aws iam role support
Browse files Browse the repository at this point in the history
  • Loading branch information
tropnikovvl committed Jan 23, 2025
1 parent 024fe25 commit 84ef959
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 12 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Run from command-line - example with minimal parameter list:
Run as docker container - example for local s3-like buckets:

```sh
docker run -p 9655:9655 -d -e S3_ENDPOINT=http://127.0.0.1:9000 -e S3_ACCESS_KEY=minioadmin -e S3_SECRET_KEY=minioadmin -e S3_BUCKET_NAMES=my-bucket-name docker.io/tropnikovvl/s3bucket_exporter:1.5.0
docker run -p 9655:9655 -d -e S3_ENDPOINT=http://127.0.0.1:9000 -e S3_ACCESS_KEY=minioadmin -e S3_SECRET_KEY=minioadmin -e S3_BUCKET_NAMES=my-bucket-name docker.io/tropnikovvl/s3bucket_exporter:1.6.0
```

Run from command-line - example for AWS
Expand All @@ -42,8 +42,9 @@ As for available flags and equivalent environment variables, here is a list:
| S3_REGION | -s3_region | S3 region name | us-east-1 | eu-west-1 |
| S3_FORCE_PATH_STYLE | -s3_force_path_style | Force use path style (bucketname not added to url) | False | True |
| LISTEN_PORT | -listen_port | Exporter listen Port cluster | :9655 | :9123 |
| LOG_LEVEL | -log_level | Log level. Info or Debug | Info | Debug |
| SCRAPE_INTERVAL | -scrape_interval | Scrape interval | 5m | 30s |
| LOG_LEVEL | -log_level | Log level. Info or Debug | Info | Debug |
| SCRAPE_INTERVAL | -scrape_interval | Scrape interval | 5m | 30s |
| USE_IAM_ROLE | -use_iam_role | Use IAM role instead of access keys | false | true |

> Warning: For security reason is not advised to use credential from command line
Expand Down
19 changes: 15 additions & 4 deletions controllers/s3talker.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type S3Conn struct {
S3ConnEndpoint string `json:"s3_conn_endpoint,omitempty"`
S3ConnRegion string `json:"s3_conn_region"`
S3ConnForcePathStyle bool `json:"s3_conn_force_path_style"`
UseIAMRole bool `json:"use_iam_role"`
}

type S3ClientInterface interface {
Expand All @@ -61,13 +62,23 @@ func getS3Client(cfg aws.Config, s3Conn S3Conn) S3ClientInterface {
if s3ClientInstance != nil {
return s3ClientInstance
}
return s3.NewFromConfig(cfg, func(o *s3.Options) {

options := func(o *s3.Options) {
if s3Conn.S3ConnEndpoint != "" {
o.BaseEndpoint = aws.String(s3Conn.S3ConnEndpoint)
}
o.Credentials = credentials.NewStaticCredentialsProvider(s3Conn.S3ConnAccessKey, s3Conn.S3ConnSecretKey, "")
if !s3Conn.UseIAMRole {
// Only set static credentials if explicitly not using IAM role
o.Credentials = credentials.NewStaticCredentialsProvider(
s3Conn.S3ConnAccessKey,
s3Conn.S3ConnSecretKey,
"",
)
}
o.UsePathStyle = s3Conn.S3ConnForcePathStyle
})
}

return s3.NewFromConfig(cfg, options)
}

// S3UsageInfo - gets S3 connection details and returns S3Summary
Expand All @@ -76,7 +87,7 @@ func S3UsageInfo(s3Conn S3Conn, s3BucketNames string) (S3Summary, error) {

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(s3Conn.S3ConnRegion))
if err != nil {
log.Errorf("Failed to create AWS session: %v", err)
log.Errorf("Failed to create AWS config: %v", err)
return summary, err
}

Expand Down
33 changes: 33 additions & 0 deletions controllers/s3talker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,36 @@ func TestCalculateBucketMetrics(t *testing.T) {
assert.Equal(t, float64(7168), size)
assert.Equal(t, float64(3), count)
}

func TestS3UsageInfo_WithIAMRole(t *testing.T) {
mockClient := new(MockS3Client)
SetS3Client(mockClient)
defer ResetS3Client()

s3Conn := S3Conn{
S3ConnRegion: "us-east-1",
S3ConnEndpoint: "s3.amazonaws.com",
UseIAMRole: true,
}

mockClient.On("ListBuckets", mock.Anything, mock.Anything, mock.Anything).Return(&s3.ListBucketsOutput{
Buckets: []types.Bucket{
{Name: aws.String("bucket1")},
},
}, nil)

mockClient.On("ListObjectsV2", mock.Anything, mock.Anything, mock.Anything).Return(&s3.ListObjectsV2Output{
Contents: []types.Object{
{Size: aws.Int64(100)},
},
IsTruncated: aws.Bool(false),
}, nil)

summary, err := S3UsageInfo(s3Conn, "bucket1")

assert.NoError(t, err)
assert.True(t, summary.S3Status)
assert.Equal(t, float64(100), summary.S3Size)
assert.Equal(t, float64(1), summary.S3ObjectNumber)
assert.Len(t, summary.S3Buckets, 1)
}
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: "3.5"
services:
s3bucketexporter:
image: docker.io/tropnikovvl/s3bucket_exporter:1.5.0
image: docker.io/tropnikovvl/s3bucket_exporter:1.6.0
restart: always
ports:
- "9655:9655"
Expand Down
4 changes: 2 additions & 2 deletions helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.2.0
version: 1.3.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.5.0"
appVersion: "1.6.0"
1 change: 1 addition & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ serviceAccount:
automount: true
# Annotations to add to the service account
annotations: {}
# eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/my-role
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
Expand Down
7 changes: 5 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var (
s3SecretKey string
s3Region string
s3ForcePathStyle bool
useIAMRole bool

metricsMutex sync.RWMutex
cachedMetrics controllers.S3Summary
Expand Down Expand Up @@ -57,6 +58,7 @@ func initFlags() {
flag.StringVar(&logLevel, "log_level", envString("LOG_LEVEL", "info"), "LOG_LEVEL")
flag.StringVar(&scrapeInterval, "scrape_interval", envString("SCRAPE_INTERVAL", "5m"), "SCRAPE_INTERVAL - eg. 30s, 5m, 1h")
flag.BoolVar(&s3ForcePathStyle, "s3_force_path_style", envBool("S3_FORCE_PATH_STYLE", false), "S3_FORCE_PATH_STYLE")
flag.BoolVar(&useIAMRole, "use_iam_role", envBool("USE_IAM_ROLE", false), "USE_IAM_ROLE - use IAM role instead of access keys")
}

// S3Collector struct
Expand Down Expand Up @@ -108,6 +110,7 @@ func updateMetrics(interval time.Duration) {
S3ConnSecretKey: s3SecretKey,
S3ConnForcePathStyle: s3ForcePathStyle,
S3ConnRegion: s3Region,
UseIAMRole: useIAMRole,
}

metrics, err := controllers.S3UsageInfo(s3Conn, s3BucketNames)
Expand Down Expand Up @@ -146,8 +149,8 @@ func main() {
}
log.SetLevel(level)

if s3AccessKey == "" || s3SecretKey == "" {
log.Fatal("S3 access key and secret key are required")
if !useIAMRole && (s3AccessKey == "" || s3SecretKey == "") {
log.Fatal("S3 access key and secret key are required when not using IAM role")
}

interval, err := time.ParseDuration(scrapeInterval)
Expand Down

0 comments on commit 84ef959

Please sign in to comment.