From 398ce2754c632822f53a664adac7767e0c81de45 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 22 Sep 2025 15:51:33 +0530 Subject: [PATCH 1/7] Security: Add endpoint to fetch the TLS data --- v2/arangodb/client_admin.go | 27 ++++++++++++++++++ v2/arangodb/client_admin_impl.go | 23 ++++++++++++++++ v2/tests/admin_test.go | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index c75cac74..cc708c73 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -71,6 +71,10 @@ type ClientAdmin interface { // by compacting the entire database system data. // The endpoint requires superuser access. CompactDatabases(ctx context.Context, opts *CompactOpts) (map[string]interface{}, error) + + // GetTLSData returns information about the server's TLS configuration. + // This call requires authentication. + GetTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) } type ClientAdminLog interface { @@ -541,3 +545,26 @@ type CompactOpts struct { // Whether or not to compact the bottommost level of data. CompactBottomMostLevel *bool `json:"compactBottomMostLevel,omitempty"` } + +// ServerName represents the hostname used in SNI configuration. +type ServerName string + +// TLSConfigObject describes the details of a TLS keyfile or CA file. +type TLSDataObject struct { + // SHA-256 hash of the whole input file (certificate or CA file). + Sha256 *string `json:"sha256,omitempty"` + // Public certificates in the chain, in PEM format. + Certificates []string `json:"certificates,omitempty"` + // SHA-256 hash of the private key (only present for keyfile). + PrivateKeySha256 *string `json:"privateKeySha256,omitempty"` +} + +// TLSConfigResponse represents the response of the TLS configuration endpoint. +type TLSDataResponse struct { + // Information about the server TLS keyfile (certificate + private key). + Keyfile TLSDataObject `json:"keyfile,omitempty"` + // Information about the CA certificates used for client verification. + ClientCA TLSDataObject `json:"clientCA,omitempty"` + // Optional mapping of server names (via SNI) to their respective TLS configurations. + SNI map[ServerName]TLSDataObject `json:"sni,omitempty"` +} diff --git a/v2/arangodb/client_admin_impl.go b/v2/arangodb/client_admin_impl.go index fee4f083..80ffb010 100644 --- a/v2/arangodb/client_admin_impl.go +++ b/v2/arangodb/client_admin_impl.go @@ -364,3 +364,26 @@ func (c *clientAdmin) CompactDatabases(ctx context.Context, opts *CompactOpts) ( return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +// GetTLSData returns information about the server's TLS configuration. +// This call requires authentication. +func (c *clientAdmin) GetTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) { + url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "server", "tls") + + var response struct { + shared.ResponseStruct `json:",inline"` + Result TLSDataResponse `json:"result,omitempty"` + } + + resp, err := connection.CallGet(ctx, c.client.connection, url, &response) + if err != nil { + return TLSDataResponse{}, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return response.Result, nil + default: + return TLSDataResponse{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index 84d5a7a6..ed77f58d 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -309,3 +309,50 @@ func Test_CompactDatabases(t *testing.T) { }) }) } + +// Test_GetTLSData checks that TLS configuration data is available and valid, skipping if not configured. +func Test_GetTLSData(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) { + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + + // Get TLS data using the client (which embeds ClientAdmin) + tlsResp, err := client.GetTLSData(ctx, db.Name()) + if err != nil { + // Skip if TLS is not configured or authentication issues + t.Logf("GetTLSData failed: %v", err) + t.Skip("Skipping TLS test - likely TLS not configured or authentication required") + } + + // Validate response structure + t.Logf("TLS Data retrieved successfully") + + // Convert to JSON for logging + tlsRespJson, err := utils.ToJSONString(tlsResp) + require.NoError(t, err) + t.Logf("TLS response: %s", tlsRespJson) + + // Basic validation - at least one field should be populated + hasData := false + if tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "" { + t.Logf("Keyfile SHA256: %s", *tlsResp.Keyfile.Sha256) + hasData = true + } + if tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "" { + t.Logf("Client CA SHA256: %s", *tlsResp.ClientCA.Sha256) + hasData = true + } + if len(tlsResp.SNI) > 0 { + t.Logf("SNI configurations found: %d", len(tlsResp.SNI)) + hasData = true + } + + if hasData { + t.Logf("TLS configuration data validated successfully") + } else { + t.Logf("TLS endpoint accessible but no TLS data returned - server may not have TLS configured") + } + }) + }) +} From 7bb37602e44706cc36f1e69217fe4fe283218e2c Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 22 Sep 2025 20:11:13 +0530 Subject: [PATCH 2/7] Security: Add end point to Reload the TLS data --- v2/arangodb/client_admin.go | 5 ++ v2/arangodb/client_admin_impl.go | 25 +++++++ v2/tests/admin_test.go | 109 ++++++++++++++++++++++++------- 3 files changed, 117 insertions(+), 22 deletions(-) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index cc708c73..c51c2b53 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -75,6 +75,11 @@ type ClientAdmin interface { // GetTLSData returns information about the server's TLS configuration. // This call requires authentication. GetTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) + + // ReloadTLSData triggers a reload of all TLS data (server key, client-auth CA) + // and returns the updated TLS configuration summary. + // Requires superuser rights. + ReloadTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) } type ClientAdminLog interface { diff --git a/v2/arangodb/client_admin_impl.go b/v2/arangodb/client_admin_impl.go index 80ffb010..a6067a0b 100644 --- a/v2/arangodb/client_admin_impl.go +++ b/v2/arangodb/client_admin_impl.go @@ -387,3 +387,28 @@ func (c *clientAdmin) GetTLSData(ctx context.Context, dbName string) (TLSDataRes return TLSDataResponse{}, response.AsArangoErrorWithCode(code) } } + +// ReloadTLSData triggers a reload of all TLS data (server key, client-auth CA) +// and returns the updated TLS configuration summary. +// Requires superuser rights. +func (c *clientAdmin) ReloadTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) { + url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "server", "tls") + + var response struct { + shared.ResponseStruct `json:",inline"` + Result TLSDataResponse `json:"result,omitempty"` + } + + // POST request, no body required + resp, err := connection.CallPost(ctx, c.client.connection, url, &response, nil) + if err != nil { + return TLSDataResponse{}, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + return response.Result, nil + // Requires superuser rights, otherwise returns 403 Forbidden + default: + return TLSDataResponse{}, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index ed77f58d..b4466187 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -320,38 +320,103 @@ func Test_GetTLSData(t *testing.T) { // Get TLS data using the client (which embeds ClientAdmin) tlsResp, err := client.GetTLSData(ctx, db.Name()) if err != nil { - // Skip if TLS is not configured or authentication issues + var arangoErr shared.ArangoError + if errors.As(err, &arangoErr) { + t.Logf("GetTLSData failed with ArangoDB error code: %d", arangoErr.Code) + switch arangoErr.Code { + case 403: + t.Skip("Skipping TLS get test - authentication/permission denied (HTTP 403)") + default: + t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) + } + } + // Skip for any other error (TLS not configured, network issues, etc.) t.Logf("GetTLSData failed: %v", err) - t.Skip("Skipping TLS test - likely TLS not configured or authentication required") + t.Skip("Skipping TLS get test - likely TLS not configured or other server issue") } - // Validate response structure - t.Logf("TLS Data retrieved successfully") + // Success! Validate response structure + t.Logf("TLS data retrieved successfully") + + // Validate TLS response data + validateTLSResponse(t, tlsResp, "Retrieved") + }) + }) +} - // Convert to JSON for logging - tlsRespJson, err := utils.ToJSONString(tlsResp) +// validateTLSResponse is a helper function to validate TLS response data +func validateTLSResponse(t testing.TB, tlsResp arangodb.TLSDataResponse, operation string) { + // Convert to JSON for logging + tlsRespJson, err := utils.ToJSONString(tlsResp) + require.NoError(t, err) + t.Logf("%s TLS response: %s", operation, tlsRespJson) + + // Basic validation - at least one field should be populated + hasData := false + if tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "" { + t.Logf("%s keyfile SHA256: %s", operation, *tlsResp.Keyfile.Sha256) + hasData = true + } + if tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "" { + t.Logf("%s client CA SHA256: %s", operation, *tlsResp.ClientCA.Sha256) + hasData = true + } + if len(tlsResp.SNI) > 0 { + t.Logf("%s SNI configurations found: %d", operation, len(tlsResp.SNI)) + hasData = true + } + if len(tlsResp.Keyfile.Certificates) > 0 { + t.Logf("%s keyfile contains %d certificates", operation, len(tlsResp.Keyfile.Certificates)) + hasData = true + } + + if hasData { + t.Logf("TLS configuration data validated successfully") + } else { + t.Logf("TLS endpoint accessible but no TLS data returned - server may not have TLS configured") + } +} + +// Test_ReloadTLSData tests TLS certificate reload functionality, skipping if superuser rights unavailable. +func Test_ReloadTLSData(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) { + db, err := client.GetDatabase(ctx, "_system", nil) require.NoError(t, err) - t.Logf("TLS response: %s", tlsRespJson) - // Basic validation - at least one field should be populated - hasData := false - if tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "" { - t.Logf("Keyfile SHA256: %s", *tlsResp.Keyfile.Sha256) - hasData = true - } - if tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "" { - t.Logf("Client CA SHA256: %s", *tlsResp.ClientCA.Sha256) - hasData = true - } - if len(tlsResp.SNI) > 0 { - t.Logf("SNI configurations found: %d", len(tlsResp.SNI)) - hasData = true + // Reload TLS data - requires superuser rights + tlsResp, err := client.ReloadTLSData(ctx, db.Name()) + if err != nil { + var arangoErr shared.ArangoError + if errors.As(err, &arangoErr) { + t.Logf("ReloadTLSData failed with ArangoDB error code: %d", arangoErr.Code) + switch arangoErr.Code { + case 403: + t.Skip("Skipping TLS reload test - superuser rights required (HTTP 403)") + default: + t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) + } + } + // Skip for any other error (TLS not configured, network issues, etc.) + t.Logf("ReloadTLSData failed: %v", err) + t.Skip("Skipping TLS reload test - likely TLS not configured or other server issue") } + // Success! Validate response structure + t.Logf("TLS data reloaded successfully") + + // Validate TLS response data (reload should always have data) + validateTLSResponse(t, tlsResp, "Reloaded") + + // For reload operations, we expect data to be present since the operation succeeded + hasData := (tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "") || + (tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "") || + len(tlsResp.SNI) > 0 || len(tlsResp.Keyfile.Certificates) > 0 + if hasData { - t.Logf("TLS configuration data validated successfully") + t.Logf("TLS configuration reloaded and validated successfully") } else { - t.Logf("TLS endpoint accessible but no TLS data returned - server may not have TLS configured") + t.Logf("TLS endpoint accessible but no TLS data reloaded - server may not have TLS configured") } }) }) From a7632c369f4f214befbdb8d0e52ba29d36cb2bc0 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 23 Sep 2025 12:26:17 +0530 Subject: [PATCH 3/7] Modified Reload the TLS data test case and endpoint signature --- v2/arangodb/client_admin.go | 2 +- v2/arangodb/client_admin_impl.go | 4 ++-- v2/tests/admin_test.go | 18 ++---------------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index c51c2b53..44492739 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -79,7 +79,7 @@ type ClientAdmin interface { // ReloadTLSData triggers a reload of all TLS data (server key, client-auth CA) // and returns the updated TLS configuration summary. // Requires superuser rights. - ReloadTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) + ReloadTLSData(ctx context.Context) (TLSDataResponse, error) } type ClientAdminLog interface { diff --git a/v2/arangodb/client_admin_impl.go b/v2/arangodb/client_admin_impl.go index a6067a0b..8627d6a2 100644 --- a/v2/arangodb/client_admin_impl.go +++ b/v2/arangodb/client_admin_impl.go @@ -391,8 +391,8 @@ func (c *clientAdmin) GetTLSData(ctx context.Context, dbName string) (TLSDataRes // ReloadTLSData triggers a reload of all TLS data (server key, client-auth CA) // and returns the updated TLS configuration summary. // Requires superuser rights. -func (c *clientAdmin) ReloadTLSData(ctx context.Context, dbName string) (TLSDataResponse, error) { - url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "server", "tls") +func (c *clientAdmin) ReloadTLSData(ctx context.Context) (TLSDataResponse, error) { + url := connection.NewUrl("_admin", "server", "tls") var response struct { shared.ResponseStruct `json:",inline"` diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index b4466187..8198a0da 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -381,11 +381,8 @@ func validateTLSResponse(t testing.TB, tlsResp arangodb.TLSDataResponse, operati func Test_ReloadTLSData(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) { - db, err := client.GetDatabase(ctx, "_system", nil) - require.NoError(t, err) - // Reload TLS data - requires superuser rights - tlsResp, err := client.ReloadTLSData(ctx, db.Name()) + tlsResp, err := client.ReloadTLSData(ctx) if err != nil { var arangoErr shared.ArangoError if errors.As(err, &arangoErr) { @@ -405,19 +402,8 @@ func Test_ReloadTLSData(t *testing.T) { // Success! Validate response structure t.Logf("TLS data reloaded successfully") - // Validate TLS response data (reload should always have data) + // Validate TLS response data validateTLSResponse(t, tlsResp, "Reloaded") - - // For reload operations, we expect data to be present since the operation succeeded - hasData := (tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "") || - (tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "") || - len(tlsResp.SNI) > 0 || len(tlsResp.Keyfile.Certificates) > 0 - - if hasData { - t.Logf("TLS configuration reloaded and validated successfully") - } else { - t.Logf("TLS endpoint accessible but no TLS data reloaded - server may not have TLS configured") - } }) }) } From fe4240da88bac17a001164ebec9097e0946cd2be Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 23 Sep 2025 15:41:10 +0530 Subject: [PATCH 4/7] Security: Add endpoint to Rotate the encryption at rest key --- v2/arangodb/client_admin.go | 15 ++++++++++++ v2/arangodb/client_admin_impl.go | 24 ++++++++++++++++++ v2/tests/admin_test.go | 42 ++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index 44492739..c7261570 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -80,6 +80,11 @@ type ClientAdmin interface { // and returns the updated TLS configuration summary. // Requires superuser rights. ReloadTLSData(ctx context.Context) (TLSDataResponse, error) + + // RotateEncryptionAtRestKey reloads the user-supplied encryption key from + // the --rocksdb.encryption-keyfolder and re-encrypts the internal encryption key. + // Requires superuser rights and is not available on Coordinators. + RotateEncryptionAtRestKey(ctx context.Context) ([]EncryptionKey, error) } type ClientAdminLog interface { @@ -573,3 +578,13 @@ type TLSDataResponse struct { // Optional mapping of server names (via SNI) to their respective TLS configurations. SNI map[ServerName]TLSDataObject `json:"sni,omitempty"` } + +// EncryptionKey represents metadata about an encryption key used for +// RocksDB encryption-at-rest in ArangoDB. +// The server exposes only the SHA-256 hash of the key for identification. +// The actual key material is never returned for security reasons. +type EncryptionKey struct { + // SHA256 is the SHA-256 hash of the encryption key, encoded as a hex string. + // This is used to uniquely identify which key is active/available. + SHA256 string `json:"sha256,omitempty"` +} diff --git a/v2/arangodb/client_admin_impl.go b/v2/arangodb/client_admin_impl.go index 8627d6a2..225452d9 100644 --- a/v2/arangodb/client_admin_impl.go +++ b/v2/arangodb/client_admin_impl.go @@ -412,3 +412,27 @@ func (c *clientAdmin) ReloadTLSData(ctx context.Context) (TLSDataResponse, error return TLSDataResponse{}, response.AsArangoErrorWithCode(code) } } + +// RotateEncryptionAtRestKey reloads the user-supplied encryption key from +// the --rocksdb.encryption-keyfolder and re-encrypts the internal encryption key. +// Requires superuser rights and is not available on Coordinators. +func (c *clientAdmin) RotateEncryptionAtRestKey(ctx context.Context) ([]EncryptionKey, error) { + url := connection.NewUrl("_admin", "server", "encryption") + + var response struct { + shared.ResponseStruct `json:",inline"` + Result []EncryptionKey `json:"result,omitempty"` + } + + // POST request, no body required + resp, err := connection.CallPost(ctx, c.client.connection, url, &response, nil) + if err != nil { + return nil, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + return response.Result, nil + default: + return nil, response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index 8198a0da..1ed45f9b 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -329,6 +329,7 @@ func Test_GetTLSData(t *testing.T) { default: t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) } + return } // Skip for any other error (TLS not configured, network issues, etc.) t.Logf("GetTLSData failed: %v", err) @@ -393,6 +394,7 @@ func Test_ReloadTLSData(t *testing.T) { default: t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) } + return } // Skip for any other error (TLS not configured, network issues, etc.) t.Logf("ReloadTLSData failed: %v", err) @@ -407,3 +409,43 @@ func Test_ReloadTLSData(t *testing.T) { }) }) } + +// Test_RotateEncryptionAtRestKey verifies that the encryption key rotation endpoint works as expected. +// The test is skipped if superuser rights are missing or the feature is disabled/not configured. +func Test_RotateEncryptionAtRestKey(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) { + // Rotate encryption at rest key - requires superuser rights + resp, err := client.RotateEncryptionAtRestKey(ctx) + if err != nil { + var arangoErr shared.ArangoError + if errors.As(err, &arangoErr) { + t.Logf("RotateEncryptionAtRestKey failed with ArangoDB error code: %d", arangoErr.Code) + switch arangoErr.Code { + case 403: + t.Skip("Skipping RotateEncryptionAtRestKey test - superuser rights required (HTTP 403)") + case 404: + t.Skip("Skipping RotateEncryptionAtRestKey test - encryption key rotation disabled (HTTP 404)") + default: + t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) + } + return + } + t.Logf("RotateEncryptionAtRestKey failed: %v", err) + } + encryptionRespJson, err := utils.ToJSONString(resp) + require.NoError(t, err) + t.Logf("RotateEncryptionAtRestKey response: %s", encryptionRespJson) + + // Success! Validate response structure + require.NotNil(t, resp, "Expected non-nil response") + t.Logf("RotateEncryptionAtRestKey succeeded with %d encryption keys", len(resp)) + + // Validate each encryption key has the required SHA256 field + for i, key := range resp { + require.NotEmpty(t, key.SHA256, "Expected encryption key %d to have non-empty SHA256", i) + t.Logf("Encryption key %d SHA256: %s", i, key.SHA256) + } + }) + }) +} From e156d7ea711d6074d8d0700f2658019c53bfb4a1 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 23 Sep 2025 15:53:41 +0530 Subject: [PATCH 5/7] Add pointer to the response --- v2/arangodb/client_admin.go | 2 +- v2/tests/admin_test.go | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index c7261570..e33b9a87 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -586,5 +586,5 @@ type TLSDataResponse struct { type EncryptionKey struct { // SHA256 is the SHA-256 hash of the encryption key, encoded as a hex string. // This is used to uniquely identify which key is active/available. - SHA256 string `json:"sha256,omitempty"` + SHA256 *string `json:"sha256,omitempty"` } diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index 1ed45f9b..8c7573f6 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -415,7 +415,8 @@ func Test_ReloadTLSData(t *testing.T) { func Test_RotateEncryptionAtRestKey(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) { - // Rotate encryption at rest key - requires superuser rights + + // Attempt to rotate encryption at rest key - requires superuser rights resp, err := client.RotateEncryptionAtRestKey(ctx) if err != nil { var arangoErr shared.ArangoError @@ -428,23 +429,29 @@ func Test_RotateEncryptionAtRestKey(t *testing.T) { t.Skip("Skipping RotateEncryptionAtRestKey test - encryption key rotation disabled (HTTP 404)") default: t.Logf("Unexpected ArangoDB error code: %d, message: %s", arangoErr.Code, arangoErr.ErrorMessage) + t.FailNow() } - return + } else { + t.Fatalf("RotateEncryptionAtRestKey failed with unexpected error: %v", err) } - t.Logf("RotateEncryptionAtRestKey failed: %v", err) + return } + + // Convert response to JSON for logging encryptionRespJson, err := utils.ToJSONString(resp) require.NoError(t, err) t.Logf("RotateEncryptionAtRestKey response: %s", encryptionRespJson) - // Success! Validate response structure + // Validate the response is not nil require.NotNil(t, resp, "Expected non-nil response") t.Logf("RotateEncryptionAtRestKey succeeded with %d encryption keys", len(resp)) - // Validate each encryption key has the required SHA256 field + // Validate each encryption key for i, key := range resp { - require.NotEmpty(t, key.SHA256, "Expected encryption key %d to have non-empty SHA256", i) - t.Logf("Encryption key %d SHA256: %s", i, key.SHA256) + // Explicit nil check for pointer + require.NotNil(t, key.SHA256, "Expected encryption key %d SHA256 not to be nil", i) + require.NotEmpty(t, *key.SHA256, "Expected encryption key %d SHA256 not to be empty", i) + t.Logf("Encryption key %d SHA256: %s", i, *key.SHA256) } }) }) From e3bc75f1aa7caad5890bc5852602fa3b95ff2e74 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 23 Sep 2025 17:07:30 +0530 Subject: [PATCH 6/7] Add note in CHANGELOG file --- v2/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/v2/CHANGELOG.md b/v2/CHANGELOG.md index 94383711..1a307b24 100644 --- a/v2/CHANGELOG.md +++ b/v2/CHANGELOG.md @@ -5,6 +5,7 @@ - Add missing endpoints from monitoring to v2 - Add missing endpoints from administration to v2 - Add missing endpoints from cluster to v2 +- Add missing endpoints from security to v2 ## [2.1.5](https://github.com/arangodb/go-driver/tree/v2.1.5) (2025-08-31) - Add tasks endpoints to v2 From ce942a4ee72a1ccceeb46332cb73505f52735649 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 24 Sep 2025 17:20:14 +0530 Subject: [PATCH 7/7] added pointers to this TLSDataResponse struct fields --- v2/arangodb/client_admin.go | 4 ++-- v2/tests/admin_test.go | 40 ++++++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/v2/arangodb/client_admin.go b/v2/arangodb/client_admin.go index e33b9a87..5e817ea9 100644 --- a/v2/arangodb/client_admin.go +++ b/v2/arangodb/client_admin.go @@ -572,9 +572,9 @@ type TLSDataObject struct { // TLSConfigResponse represents the response of the TLS configuration endpoint. type TLSDataResponse struct { // Information about the server TLS keyfile (certificate + private key). - Keyfile TLSDataObject `json:"keyfile,omitempty"` + Keyfile *TLSDataObject `json:"keyfile,omitempty"` // Information about the CA certificates used for client verification. - ClientCA TLSDataObject `json:"clientCA,omitempty"` + ClientCA *TLSDataObject `json:"clientCA,omitempty"` // Optional mapping of server names (via SNI) to their respective TLS configurations. SNI map[ServerName]TLSDataObject `json:"sni,omitempty"` } diff --git a/v2/tests/admin_test.go b/v2/tests/admin_test.go index 8c7573f6..64f91b99 100644 --- a/v2/tests/admin_test.go +++ b/v2/tests/admin_test.go @@ -23,6 +23,7 @@ import ( "context" "errors" "net/http" + "strings" "testing" "time" @@ -347,18 +348,34 @@ func Test_GetTLSData(t *testing.T) { // validateTLSResponse is a helper function to validate TLS response data func validateTLSResponse(t testing.TB, tlsResp arangodb.TLSDataResponse, operation string) { - // Convert to JSON for logging - tlsRespJson, err := utils.ToJSONString(tlsResp) - require.NoError(t, err) - t.Logf("%s TLS response: %s", operation, tlsRespJson) - // Basic validation - at least one field should be populated hasData := false - if tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "" { - t.Logf("%s keyfile SHA256: %s", operation, *tlsResp.Keyfile.Sha256) - hasData = true + if tlsResp.Keyfile != nil { + if tlsResp.Keyfile.Sha256 != nil && *tlsResp.Keyfile.Sha256 != "" { + t.Logf("%s keyfile SHA256: %s", operation, *tlsResp.Keyfile.Sha256) + hasData = true + } + if len(tlsResp.Keyfile.Certificates) > 0 { + t.Logf("%s keyfile contains %d certificates", operation, len(tlsResp.Keyfile.Certificates)) + hasData = true + + // Validate certificate content (basic PEM format check) + for i, cert := range tlsResp.Keyfile.Certificates { + require.NotEmpty(t, cert, "Certificate %d should not be empty", i) + // Basic PEM format validation + if !strings.Contains(cert, "-----BEGIN CERTIFICATE-----") { + t.Logf("Warning: Certificate %d may not be in PEM format", i) + } else { + t.Logf("Certificate %d appears to be valid PEM format", i) + } + } + } + if tlsResp.Keyfile.PrivateKeySha256 != nil && *tlsResp.Keyfile.PrivateKeySha256 != "" { + t.Logf("%s keyfile private key SHA256: %s", operation, *tlsResp.Keyfile.PrivateKeySha256) + hasData = true + } } - if tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "" { + if tlsResp.ClientCA != nil && tlsResp.ClientCA.Sha256 != nil && *tlsResp.ClientCA.Sha256 != "" { t.Logf("%s client CA SHA256: %s", operation, *tlsResp.ClientCA.Sha256) hasData = true } @@ -366,11 +383,6 @@ func validateTLSResponse(t testing.TB, tlsResp arangodb.TLSDataResponse, operati t.Logf("%s SNI configurations found: %d", operation, len(tlsResp.SNI)) hasData = true } - if len(tlsResp.Keyfile.Certificates) > 0 { - t.Logf("%s keyfile contains %d certificates", operation, len(tlsResp.Keyfile.Certificates)) - hasData = true - } - if hasData { t.Logf("TLS configuration data validated successfully") } else {