Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 1.6.1 - 2025-09-11

* Add customized cloud account id setting via `SetCloudAccountID` function

## 1.6.0 - 2025-06-30

* Add active content detection support via `SetActiveContentEnable` function
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,21 @@ You can disable digest calculation by calling the `SetDigestDisable` function:
client.SetDigestDisable()
```

### Set Cloud Account ID

You can set a cloud account ID that will be automatically appended to all scan tags in the format `cloudAccountId=value`:

```go
err := client.SetCloudAccountID("633537927402")
if err != nil {
// Handle error - cloudAccountID too long
}
```

**Note**:
- The total tag length (including `cloudAccountId=` prefix) cannot exceed 63 characters
- Using cloud account ID occupies one tag slot, reducing max customer tags from 8 to 7

## Golang Client SDK API Reference

### ```func NewClient(key string, region string) (c *AmaasClient, e error)```
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.0
1.6.1
100 changes: 72 additions & 28 deletions grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,21 +218,22 @@ func (reader *AmaasClientBufferReader) Hash(algorithm string) (string, error) {
///////////////////////////////////////

type AmaasClient struct {
conn *grpc.ClientConn
isC1Token bool
authKey string
addr string
useTLS bool
caCert string
verifyCert bool
timeoutSecs int
appName string
archHandler AmaasClientArchiveHandler
pml bool
feedback bool
verbose bool
activeContent bool
digest bool
conn *grpc.ClientConn
isC1Token bool
authKey string
addr string
useTLS bool
caCert string
verifyCert bool
timeoutSecs int
appName string
archHandler AmaasClientArchiveHandler
pml bool
feedback bool
verbose bool
activeContent bool
digest bool
cloudAccountID string
}

func getHashValue(dataReader AmaasClientReader) (string, string, error) {
Expand Down Expand Up @@ -446,8 +447,7 @@ func runUploadLoop(stream pb.Scan_RunClient, dataReader AmaasClientReader, bulk
return
}

func (ac *AmaasClient) bufferScanRun(buffer []byte, identifier string, tags []string) (string, error) {

func (ac *AmaasClient) bufferScanRun(ctx context.Context, buffer []byte, identifier string, tags []string) (string, error) {
if ac.conn == nil {
return "", makeInternalError(MSG("MSG_ID_ERR_CLIENT_NOT_READY"))
}
Expand All @@ -458,18 +458,20 @@ func (ac *AmaasClient) bufferScanRun(buffer []byte, identifier string, tags []st
}
defer bufferReader.Close()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(ac.timeoutSecs))
ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(ac.timeoutSecs))

ctx = ac.buildAuthContext(ctx)

ctx = ac.buildAppNameContext(ctx)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), bufferReader, tags, ac.pml, true, ac.feedback,
tags = ac.appendCloudAccountIDToTags(tags)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), bufferReader,
tags, ac.pml, true, ac.feedback,
ac.verbose, ac.activeContent, ac.digest)
}

func (ac *AmaasClient) fileScanRun(fileName string, tags []string) (string, error) {

func (ac *AmaasClient) fileScanRun(ctx context.Context, fileName string, tags []string) (string, error) {
if ac.conn == nil {
return "", makeInternalError(MSG("MSG_ID_ERR_CLIENT_NOT_READY"))
}
Expand All @@ -478,40 +480,46 @@ func (ac *AmaasClient) fileScanRun(fileName string, tags []string) (string, erro
return ac.archHandler.fileScanRun(fileName)
}

return ac.fileScanRunNormalFile(fileName, tags)
return ac.fileScanRunNormalFile(ctx, fileName, tags)
}

func (ac *AmaasClient) fileScanRunNormalFile(fileName string, tags []string) (string, error) {
func (ac *AmaasClient) fileScanRunNormalFile(ctx context.Context, fileName string, tags []string) (string, error) {

fileReader, err := InitFileReader(fileName)
if err != nil {
return "", makeInternalError(fmt.Sprintf(MSG("MSG_ID_ERR_CLIENT_ERROR"), err.Error()))
}
defer fileReader.Close()

ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(ac.timeoutSecs))
ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(ac.timeoutSecs))

ctx = ac.buildAuthContext(ctx)

ctx = ac.buildAppNameContext(ctx)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), fileReader, tags, ac.pml, true, ac.feedback,
tags = ac.appendCloudAccountIDToTags(tags)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), fileReader,
tags, ac.pml, true, ac.feedback,
ac.verbose, ac.activeContent, ac.digest)
}

func (ac *AmaasClient) readerScanRun(reader AmaasClientReader, tags []string) (string, error) {
func (ac *AmaasClient) readerScanRun(ctx context.Context, reader AmaasClientReader, tags []string) (string, error) {

if ac.conn == nil {
return "", makeInternalError(MSG("MSG_ID_ERR_CLIENT_NOT_READY"))
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(ac.timeoutSecs))
ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(ac.timeoutSecs))

ctx = ac.buildAuthContext(ctx)

ctx = ac.buildAppNameContext(ctx)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), reader, tags, ac.pml, true, ac.feedback,
tags = ac.appendCloudAccountIDToTags(tags)

return scanRun(ctx, cancel, pb.NewScanClient(ac.conn), reader,
tags, ac.pml, true, ac.feedback,
ac.verbose, ac.activeContent, ac.digest)
}

Expand Down Expand Up @@ -1127,6 +1135,22 @@ func (ac *AmaasClient) SetDigestDisable() {
ac.digest = false
}

func (ac *AmaasClient) SetCloudAccountID(cloudAccountID string) error {
if cloudAccountID == "" {
ac.cloudAccountID = cloudAccountID
return nil
}

// Calculate the total tag length with "cloudAccountId=" prefix
cloudAccountTag := fmt.Sprintf("cloudAccountId=%s", cloudAccountID)
if len(cloudAccountTag) > maxTagSize {
return fmt.Errorf("cloudAccountID tag 'cloudAccountId=%s' exceeds maximum tag size of %d characters", cloudAccountID, maxTagSize)
}

ac.cloudAccountID = cloudAccountID
return nil
}

func validateTags(tags []string) error {
if len(tags) == 0 {
return errors.New("tags cannot be empty")
Expand All @@ -1146,3 +1170,23 @@ func validateTags(tags []string) error {
}
return nil
}

func (ac *AmaasClient) appendCloudAccountIDToTags(tags []string) []string {
if ac.cloudAccountID == "" {
return tags
}

cloudAccountTag := fmt.Sprintf("cloudAccountId=%s", ac.cloudAccountID)

// Check if the cloudAccountTag exceeds maxTagSize (63 characters)
if len(cloudAccountTag) > maxTagSize {
logMsg(LogLevelWarning, "cloudAccountId tag exceeds maximum tag size (%d), skipping", maxTagSize)
return tags
}

if tags == nil {
return []string{cloudAccountTag}
}

return append(tags, cloudAccountTag)
}
146 changes: 146 additions & 0 deletions grpc_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,3 +642,149 @@ func generateJwtToken() (string, error) {

return ss, nil
}

//
// CloudAccountID related tests
//

func TestSetCloudAccountIDValid(t *testing.T) {
ac := &AmaasClient{}

// Test with AWS account ID (12 digits)
err := ac.SetCloudAccountID("633537927402")
assert.Nil(t, err)
assert.Equal(t, "633537927402", ac.cloudAccountID)

// Test with Azure UUID-v4 (36 characters)
err = ac.SetCloudAccountID("a47ac10b-58cc-4372-a567-0e02b2c3d479")
assert.Nil(t, err)
assert.Equal(t, "a47ac10b-58cc-4372-a567-0e02b2c3d479", ac.cloudAccountID)

// Test with exactly 48 characters (63 - 15 for "cloudAccountId=")
longButValid := "123456789012345678901234567890123456789012345678"
err = ac.SetCloudAccountID(longButValid)
assert.Nil(t, err)
assert.Equal(t, longButValid, ac.cloudAccountID)
}

func TestSetCloudAccountIDTooLong(t *testing.T) {
ac := &AmaasClient{}

// Test with string longer than 48 characters (49 chars)
tooLong := "1234567890123456789012345678901234567890123456789"
err := ac.SetCloudAccountID(tooLong)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "exceeds maximum tag size of 63 characters")
assert.Equal(t, "", ac.cloudAccountID)
}

func TestSetCloudAccountIDEmpty(t *testing.T) {
ac := &AmaasClient{}

// Test with empty string (should be allowed)
err := ac.SetCloudAccountID("")
assert.Nil(t, err)
assert.Equal(t, "", ac.cloudAccountID)
}

func TestAppendCloudAccountIDToTagsWithEmpty(t *testing.T) {
ac := &AmaasClient{}
ac.cloudAccountID = ""

// Test with nil tags
result := ac.appendCloudAccountIDToTags(nil)
assert.Nil(t, result)

// Test with empty tags slice
tags := []string{}
result = ac.appendCloudAccountIDToTags(tags)
assert.Equal(t, tags, result)

// Test with existing tags
tags = []string{"tag1", "tag2"}
result = ac.appendCloudAccountIDToTags(tags)
assert.Equal(t, tags, result)
}

func TestAppendCloudAccountIDToTagsWithValue(t *testing.T) {
ac := &AmaasClient{}
ac.cloudAccountID = "633537927402"

// Test with nil tags
result := ac.appendCloudAccountIDToTags(nil)
expected := []string{"cloudAccountId=633537927402"}
assert.Equal(t, expected, result)

// Test with empty tags slice
tags := []string{}
result = ac.appendCloudAccountIDToTags(tags)
expected = []string{"cloudAccountId=633537927402"}
assert.Equal(t, expected, result)

// Test with existing tags
tags = []string{"tag1", "tag2"}
result = ac.appendCloudAccountIDToTags(tags)
expected = []string{"tag1", "tag2", "cloudAccountId=633537927402"}
assert.Equal(t, expected, result)
}

func TestAppendCloudAccountIDToTagsImmutability(t *testing.T) {
ac := &AmaasClient{}
ac.cloudAccountID = "633537927402"

// Test that original tags slice is not modified
originalTags := []string{"tag1", "tag2"}
originalTagsCopy := make([]string, len(originalTags))
copy(originalTagsCopy, originalTags)

result := ac.appendCloudAccountIDToTags(originalTags)

// Original slice should remain unchanged
assert.Equal(t, originalTagsCopy, originalTags)
// Result should contain cloudAccountID
expected := []string{"tag1", "tag2", "cloudAccountId=633537927402"}
assert.Equal(t, expected, result)
}

func TestCloudAccountIDIntegrationWithScanMethods(t *testing.T) {
// Create a mock AmaasClient with connection set to nil (to trigger early return)
ac := &AmaasClient{
conn: nil,
}

// Set cloudAccountID
err := ac.SetCloudAccountID("633537927402")
assert.Nil(t, err)

// Test ScanFile with cloudAccountID
_, err = ac.ScanFile("nonexistent.txt", []string{"tag1"})
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Client is not ready")

// Test ScanBuffer with cloudAccountID
_, err = ac.ScanBuffer([]byte("test"), "buffer", []string{"tag1"})
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Client is not ready")

// Test ScanReader with cloudAccountID
bufferReader, _ := InitBufferReader([]byte("test"), "reader")
_, err = ac.ScanReader(bufferReader, []string{"tag1"})
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Client is not ready")

bufferReader.Close()
}

func TestAppendCloudAccountIDToTagsTooLong(t *testing.T) {
ac := &AmaasClient{}

// Set a cloudAccountID that would make the tag too long (49 characters)
ac.cloudAccountID = "1234567890123456789012345678901234567890123456789" // 49 chars

// Test with existing tags - should skip the cloudAccountID
tags := []string{"tag1", "tag2"}
result := ac.appendCloudAccountIDToTags(tags)

// Should return original tags unchanged (cloudAccountID skipped due to length)
assert.Equal(t, tags, result)
}
24 changes: 21 additions & 3 deletions sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,36 @@ func (ac *AmaasClient) Destroy() {
//

func (ac *AmaasClient) ScanFile(filePath string, tags []string) (resp string, e error) {
ctx := context.Background()
currentLogLevel = getLogLevel()
return ac.fileScanRun(filePath, tags)
return ac.fileScanRun(ctx, filePath, tags)
}

func (ac *AmaasClient) ScanFileWithContext(ctx context.Context, filePath string, tags []string) (resp string, e error) {
currentLogLevel = getLogLevel()
return ac.fileScanRun(ctx, filePath, tags)
}

func (ac *AmaasClient) ScanBuffer(buffer []byte, identifier string, tags []string) (resp string, e error) {
ctx := context.Background()
currentLogLevel = getLogLevel()
return ac.bufferScanRun(buffer, identifier, tags)
return ac.bufferScanRun(ctx, buffer, identifier, tags)
}

func (ac *AmaasClient) ScanBufferWithContext(ctx context.Context, buffer []byte, identifier string, tags []string) (resp string, e error) {
currentLogLevel = getLogLevel()
return ac.bufferScanRun(ctx, buffer, identifier, tags)
}

func (ac *AmaasClient) ScanReader(reader AmaasClientReader, tags []string) (resp string, e error) {
ctx := context.Background()
currentLogLevel = getLogLevel()
return ac.readerScanRun(ctx, reader, tags)
}

func (ac *AmaasClient) ScanReaderWithContext(ctx context.Context, reader AmaasClientReader, tags []string) (resp string, e error) {
currentLogLevel = getLogLevel()
return ac.readerScanRun(reader, tags)
return ac.readerScanRun(ctx, reader, tags)
}

func (ac *AmaasClient) DumpConfig() (output string) {
Expand Down