Skip to content

Commit 9380be9

Browse files
feat: Support OIDC configs in mongodbatlas_stream_connection (#3680)
* CLOUDP-338207: Terraform: support OIDC configs in mongodbatlas_stream_connection * Fix lint and changelog issues * Fix changelog issues * Fix lint issues * Fix fmt issues * Refactor by comments * Refactor by comments
1 parent 7b7a8ca commit 9380be9

File tree

11 files changed

+327
-38
lines changed

11 files changed

+327
-38
lines changed

.changelog/3680.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
```release-note:enhancement
2+
resource/mongodbatlas_stream_connection: Adds new authentication mechanism(OIDC) to the Kafka connection
3+
```
4+
5+
```release-note:enhancement
6+
data-source/mongodbatlas_stream_connection: Adds new authentication mechanism(OIDC) to the Kafka connection
7+
```
8+
9+
```release-note:enhancement
10+
data-source/mongodbatlas_stream_connections: Adds new authentication mechanism(OIDC) to the Kafka connection
11+
```

docs/data-sources/stream_connection.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,15 @@ If `type` is of value `Https` the following additional attributes are defined:
4747

4848
### Authentication
4949

50-
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, or `SCRAM-512`.
50+
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, `SCRAM-512`, or `OAUTHBEARER`.
5151
* `username` - Username of the account to connect to the Kafka cluster.
5252
* `password` - Password of the account to connect to the Kafka cluster.
53+
* `token_endpoint_url` - OAUTH issuer(IdP provider) token endpoint HTTP(S) URI used to retrieve the token.
54+
* `client_id` - Public identifier for the Kafka client.
55+
* `client_secret` - Secret known only to the Kafka client and the authorization server.
56+
* `scope` - Kafka clients use this to specify the scope of the access request to the broker.
57+
* `sasl_oauthbearer_extensions` - Additional information to be provided to the Kafka broker.
58+
* `https_ca_pem` - The CA certificates as a PEM string.
5359

5460
### Security
5561

docs/data-sources/stream_connections.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,15 @@ If `type` is of value `Https` the following additional attributes are defined:
5858

5959
### Authentication
6060

61-
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, or `SCRAM-512`.
61+
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, `SCRAM-512`, or `OAUTHBEARER`.
6262
* `username` - Username of the account to connect to the Kafka cluster.
6363
* `password` - Password of the account to connect to the Kafka cluster.
64+
* `token_endpoint_url` - OAUTH issuer(IdP provider) token endpoint HTTP(S) URI used to retrieve the token.
65+
* `client_id` - Public identifier for the Kafka client. It must be unique across all clients that the authorization server handles.
66+
* `client_secret` - Secret known only to the Kafka client and the authorization server.
67+
* `scope` - Kafka clients use this to specify the scope of the access request to the broker.
68+
* `sasl_oauthbearer_extensions` - Additional information to be provided to the Kafka broker.
69+
* `https_ca_pem` - The CA certificates as a PEM string.
6470

6571
### Security
6672

docs/resources/stream_connection.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ resource "mongodbatlas_stream_connection" "test" {
6262
}
6363
```
6464

65+
### Example Kafka SASL OAuthbearer Connection
66+
67+
```terraform
68+
resource "mongodbatlas_stream_connection" "example-kafka-oauthbearer" {
69+
project_id = var.project_id
70+
instance_name = mongodbatlas_stream_instance.example.instance_name
71+
connection_name = "KafkaOAuthbearerConnection"
72+
type = "Kafka"
73+
authentication = {
74+
mechanism = "OAUTHBEARER"
75+
token_endpoint_url = "https://example.com/oauth/token"
76+
client_id = "auth0Client"
77+
client_secret = var.kafka_client_secret
78+
scope = "read:messages write:messages"
79+
sasl_oauthbearer_extensions = "logicalCluster=lkc-kmom,identityPoolId=pool-lAr"
80+
https_ca_pem = "pemtext"
81+
}
82+
bootstrap_servers = "localhost:9092,localhost:9092"
83+
config = {
84+
"auto.offset.reset" : "earliest"
85+
}
86+
security = {
87+
protocol = "SASL_PLAINTEXT"
88+
}
89+
networking = {
90+
access = {
91+
type = "PUBLIC"
92+
}
93+
}
94+
}
95+
```
96+
6597
### Example Kafka SASL SSL Connection
6698

6799
```terraform
@@ -148,6 +180,12 @@ If `type` is of value `Https` the following additional attributes are defined:
148180
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, or `SCRAM-512`.
149181
* `username` - Username of the account to connect to the Kafka cluster.
150182
* `password` - Password of the account to connect to the Kafka cluster.
183+
* `token_endpoint_url` - OAUTH issuer(IdP provider) token endpoint HTTP(S) URI used to retrieve the token.
184+
* `client_id` - Public identifier for the Kafka client.
185+
* `client_secret` - Secret known only to the Kafka client and the authorization server.
186+
* `scope` - Kafka clients use this to specify the scope of the access request to the broker.
187+
* `sasl_oauthbearer_extensions` - Additional information to be provided to the Kafka broker.
188+
* `https_ca_pem` - The CA certificates as a PEM string.
151189

152190
### Security
153191

examples/mongodbatlas_stream_connection/main.tf

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,34 @@ resource "mongodbatlas_stream_connection" "example-kafka-plaintext" {
5656
}
5757
}
5858

59+
resource "mongodbatlas_stream_connection" "example-kafka-oauthbearer" {
60+
project_id = var.project_id
61+
instance_name = mongodbatlas_stream_instance.example.instance_name
62+
connection_name = "KafkaOAuthbearerConnection"
63+
type = "Kafka"
64+
authentication = {
65+
mechanism = "OAUTHBEARER"
66+
token_endpoint_url = "https://example.com/oauth/token"
67+
client_id = "auth0Client"
68+
client_secret = var.kafka_client_secret
69+
scope = "read:messages write:messages"
70+
sasl_oauthbearer_extensions = "logicalCluster=lkc-kmom,identityPoolId=pool-lAr"
71+
https_ca_pem = "pemtext"
72+
}
73+
bootstrap_servers = "localhost:9092,localhost:9092"
74+
config = {
75+
"auto.offset.reset" : "earliest"
76+
}
77+
security = {
78+
protocol = "SASL_PLAINTEXT"
79+
}
80+
networking = {
81+
access = {
82+
type = "PUBLIC"
83+
}
84+
}
85+
}
86+
5987
resource "mongodbatlas_stream_connection" "example-kafka-ssl" {
6088
project_id = var.project_id
6189
instance_name = mongodbatlas_stream_instance.example.instance_name

examples/mongodbatlas_stream_connection/variables.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ variable "kafka_password" {
2121
type = string
2222
}
2323

24+
variable "kafka_client_secret" {
25+
description = "Secret known only to the Kafka client and the authorization server"
26+
type = string
27+
}
28+
2429
variable "kafka_ssl_cert" {
2530
description = "Public certificate used for SASL_SSL configuration to connect to your Kafka cluster"
2631
type = string

internal/service/streamconnection/model_stream_connection.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ func NewStreamConnectionReq(ctx context.Context, plan *TFStreamConnectionModel)
2727
return nil, diags
2828
}
2929
streamConnection.Authentication = &admin.StreamsKafkaAuthentication{
30-
Mechanism: authenticationModel.Mechanism.ValueStringPointer(),
31-
Password: authenticationModel.Password.ValueStringPointer(),
32-
Username: authenticationModel.Username.ValueStringPointer(),
30+
Mechanism: authenticationModel.Mechanism.ValueStringPointer(),
31+
Password: authenticationModel.Password.ValueStringPointer(),
32+
Username: authenticationModel.Username.ValueStringPointer(),
33+
TokenEndpointUrl: authenticationModel.TokenEndpointURL.ValueStringPointer(),
34+
ClientId: authenticationModel.ClientID.ValueStringPointer(),
35+
ClientSecret: authenticationModel.ClientSecret.ValueStringPointer(),
36+
Scope: authenticationModel.Scope.ValueStringPointer(),
37+
SaslOauthbearerExtensions: authenticationModel.SaslOauthbearerExtensions.ValueStringPointer(),
38+
HttpsCaPem: authenticationModel.HTTPSCaPem.ValueStringPointer(),
3339
}
3440
}
3541
if !plan.Security.IsNull() {
@@ -215,8 +221,13 @@ func NewTFStreamConnection(ctx context.Context, projID, instanceName string, cur
215221
func newTFConnectionAuthenticationModel(ctx context.Context, currAuthConfig *types.Object, authResp *admin.StreamsKafkaAuthentication) (*types.Object, diag.Diagnostics) {
216222
if authResp != nil {
217223
resultAuthModel := TFConnectionAuthenticationModel{
218-
Mechanism: types.StringPointerValue(authResp.Mechanism),
219-
Username: types.StringPointerValue(authResp.Username),
224+
Mechanism: types.StringPointerValue(authResp.Mechanism),
225+
Username: types.StringPointerValue(authResp.Username),
226+
TokenEndpointURL: types.StringPointerValue(authResp.TokenEndpointUrl),
227+
ClientID: types.StringPointerValue(authResp.ClientId),
228+
Scope: types.StringPointerValue(authResp.Scope),
229+
SaslOauthbearerExtensions: types.StringPointerValue(authResp.SaslOauthbearerExtensions),
230+
HTTPSCaPem: types.StringPointerValue(authResp.HttpsCaPem),
220231
}
221232

222233
if currAuthConfig != nil && !currAuthConfig.IsNull() { // if config is available (create & update of resource) password value is set in new state
@@ -225,6 +236,7 @@ func newTFConnectionAuthenticationModel(ctx context.Context, currAuthConfig *typ
225236
return nil, diags
226237
}
227238
resultAuthModel.Password = configAuthModel.Password
239+
resultAuthModel.ClientSecret = configAuthModel.ClientSecret
228240
}
229241

230242
resultObject, diags := types.ObjectValueFrom(ctx, ConnectionAuthenticationObjectType.AttrTypes, resultAuthModel)

internal/service/streamconnection/model_stream_connection_test.go

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@ import (
1111
)
1212

1313
const (
14-
connectionName = "Connection"
15-
typeValue = ""
16-
clusterName = "Cluster0"
17-
dummyProjectID = "111111111111111111111111"
18-
instanceName = "InstanceName"
19-
authMechanism = "PLAIN"
20-
authUsername = "user1"
14+
connectionName = "Connection"
15+
typeValue = ""
16+
clusterName = "Cluster0"
17+
dummyProjectID = "111111111111111111111111"
18+
instanceName = "InstanceName"
19+
authMechanism = "PLAIN"
20+
authMechanismOAuth = "OAUTHBEARER"
21+
authUsername = "user1"
22+
clientID = "auth0Client"
23+
clientSecret = "secret"
24+
// #nosec G101
25+
tokenEndpointURL = "https://your-domain.com/oauth2/token"
26+
scope = "read:messages write:messages"
27+
saslOauthbearerExtentions = "logicalCluster=cluster-kmo17m,identityPoolId=pool-l7Arl"
28+
httpsCaPem = "MHWER3343"
2129
securityProtocol = "SASL_SSL"
2230
bootstrapServers = "localhost:9092,another.host:9092"
2331
dbRole = "customRole"
@@ -50,6 +58,7 @@ type sdkToTFModelTestCase struct {
5058

5159
func TestStreamConnectionSDKToTFModel(t *testing.T) {
5260
var authConfigWithPasswordDefined = tfAuthenticationObject(t, authMechanism, authUsername, "raw password")
61+
var authConfigWithOAuth = tfAuthenticationObjectForOAuth(t, authMechanismOAuth, clientID, clientSecret, tokenEndpointURL, scope, saslOauthbearerExtentions, httpsCaPem)
5362

5463
testCases := []sdkToTFModelTestCase{
5564
{
@@ -146,6 +155,44 @@ func TestStreamConnectionSDKToTFModel(t *testing.T) {
146155
Headers: types.MapNull(types.StringType),
147156
},
148157
},
158+
{
159+
name: "Kafka connection type SDK response for OAuthBearer authentication",
160+
SDKResp: &admin.StreamsConnection{
161+
Name: admin.PtrString(connectionName),
162+
Type: admin.PtrString("Kafka"),
163+
Authentication: &admin.StreamsKafkaAuthentication{
164+
Mechanism: admin.PtrString(authMechanismOAuth),
165+
ClientId: admin.PtrString(clientID),
166+
TokenEndpointUrl: admin.PtrString(tokenEndpointURL),
167+
Scope: admin.PtrString(scope),
168+
SaslOauthbearerExtensions: admin.PtrString(saslOauthbearerExtentions),
169+
HttpsCaPem: admin.PtrString(httpsCaPem),
170+
},
171+
BootstrapServers: admin.PtrString(bootstrapServers),
172+
Config: &configMap,
173+
Security: &admin.StreamsKafkaSecurity{
174+
Protocol: admin.PtrString(securityProtocol),
175+
BrokerPublicCertificate: admin.PtrString(DummyCACert),
176+
},
177+
},
178+
providedProjID: dummyProjectID,
179+
providedInstanceName: instanceName,
180+
providedAuthConfig: &authConfigWithOAuth,
181+
expectedTFModel: &streamconnection.TFStreamConnectionModel{
182+
ProjectID: types.StringValue(dummyProjectID),
183+
InstanceName: types.StringValue(instanceName),
184+
ConnectionName: types.StringValue(connectionName),
185+
Type: types.StringValue("Kafka"),
186+
Authentication: tfAuthenticationObjectForOAuth(t, authMechanismOAuth, clientID, clientSecret, tokenEndpointURL, scope, saslOauthbearerExtentions, httpsCaPem), // password value is obtained from config, not api resp.
187+
BootstrapServers: types.StringValue(bootstrapServers),
188+
Config: tfConfigMap(t, configMap),
189+
Security: tfSecurityObject(t, DummyCACert, securityProtocol),
190+
DBRoleToExecute: types.ObjectNull(streamconnection.DBRoleToExecuteObjectType.AttrTypes),
191+
Networking: types.ObjectNull(streamconnection.NetworkingObjectType.AttrTypes),
192+
AWS: types.ObjectNull(streamconnection.AWSObjectType.AttrTypes),
193+
Headers: types.MapNull(types.StringType),
194+
},
195+
},
149196
{
150197
name: "Kafka connection type SDK response with no optional values provided",
151198
SDKResp: &admin.StreamsConnection{
@@ -596,6 +643,23 @@ func tfAuthenticationObject(t *testing.T, mechanism, username, password string)
596643
return auth
597644
}
598645

646+
func tfAuthenticationObjectForOAuth(t *testing.T, mechanism, clientID, clientSecret, tokenEndpointURL, scope, saslOauthbearerExtensions, httpsCaPem string) types.Object {
647+
t.Helper()
648+
auth, diags := types.ObjectValueFrom(t.Context(), streamconnection.ConnectionAuthenticationObjectType.AttrTypes, streamconnection.TFConnectionAuthenticationModel{
649+
Mechanism: types.StringValue(mechanism),
650+
ClientID: types.StringValue(clientID),
651+
ClientSecret: types.StringValue(clientSecret),
652+
TokenEndpointURL: types.StringValue(tokenEndpointURL),
653+
Scope: types.StringValue(scope),
654+
SaslOauthbearerExtensions: types.StringValue(saslOauthbearerExtensions),
655+
HTTPSCaPem: types.StringValue(httpsCaPem),
656+
})
657+
if diags.HasError() {
658+
t.Errorf("failed to create terraform data model: %s", diags.Errors()[0].Summary())
659+
}
660+
return auth
661+
}
662+
599663
func tfAuthenticationObjectWithNoPassword(t *testing.T, mechanism, username string) types.Object {
600664
t.Helper()
601665
auth, diags := types.ObjectValueFrom(t.Context(), streamconnection.ConnectionAuthenticationObjectType.AttrTypes, streamconnection.TFConnectionAuthenticationModel{

internal/service/streamconnection/resource_schema.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,25 @@ func ResourceSchema(ctx context.Context) schema.Schema {
8282
"username": schema.StringAttribute{
8383
Optional: true,
8484
},
85+
"token_endpoint_url": schema.StringAttribute{
86+
Optional: true,
87+
},
88+
"client_id": schema.StringAttribute{
89+
Optional: true,
90+
},
91+
"client_secret": schema.StringAttribute{
92+
Optional: true,
93+
Sensitive: true,
94+
},
95+
"scope": schema.StringAttribute{
96+
Optional: true,
97+
},
98+
"sasl_oauthbearer_extensions": schema.StringAttribute{
99+
Optional: true,
100+
},
101+
"https_ca_pem": schema.StringAttribute{
102+
Optional: true,
103+
},
85104
},
86105
},
87106
"bootstrap_servers": schema.StringAttribute{

internal/service/streamconnection/resource_stream_connection.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,27 @@ type TFStreamConnectionModel struct {
5454
}
5555

5656
type TFConnectionAuthenticationModel struct {
57-
Mechanism types.String `tfsdk:"mechanism"`
58-
Password types.String `tfsdk:"password"`
59-
Username types.String `tfsdk:"username"`
57+
Mechanism types.String `tfsdk:"mechanism"`
58+
Password types.String `tfsdk:"password"`
59+
Username types.String `tfsdk:"username"`
60+
TokenEndpointURL types.String `tfsdk:"token_endpoint_url"`
61+
ClientID types.String `tfsdk:"client_id"`
62+
ClientSecret types.String `tfsdk:"client_secret"`
63+
Scope types.String `tfsdk:"scope"`
64+
SaslOauthbearerExtensions types.String `tfsdk:"sasl_oauthbearer_extensions"`
65+
HTTPSCaPem types.String `tfsdk:"https_ca_pem"`
6066
}
6167

6268
var ConnectionAuthenticationObjectType = types.ObjectType{AttrTypes: map[string]attr.Type{
63-
"mechanism": types.StringType,
64-
"password": types.StringType,
65-
"username": types.StringType,
69+
"mechanism": types.StringType,
70+
"password": types.StringType,
71+
"username": types.StringType,
72+
"token_endpoint_url": types.StringType,
73+
"client_id": types.StringType,
74+
"client_secret": types.StringType,
75+
"scope": types.StringType,
76+
"sasl_oauthbearer_extensions": types.StringType,
77+
"https_ca_pem": types.StringType,
6678
}}
6779

6880
type TFConnectionSecurityModel struct {

0 commit comments

Comments
 (0)