Skip to content

Commit 8d9c0aa

Browse files
CLOUDP-338207: Terraform: support OIDC configs in mongodbatlas_stream_connection
1 parent d3fff61 commit 8d9c0aa

File tree

10 files changed

+307
-31
lines changed

10 files changed

+307
-31
lines changed

docs/data-sources/stream_connection.md

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

4444
### Authentication
4545

46-
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, or `SCRAM-512`.
46+
* `mechanism` - Style of authentication. Can be one of `PLAIN`, `SCRAM-256`, `SCRAM-512`, or `OAUTHBEARER`.
4747
* `username` - Username of the account to connect to the Kafka cluster.
4848
* `password` - Password of the account to connect to the Kafka cluster.
49+
* `token_endpoint_url` - OAUTH issuer token endpoint HTTP(S) URI used to retrieve the token.
50+
* `client_id` - Public identifier for the kafka client. It must be unique across all clients that the authorization server handles.
51+
* `client_secret` - Secret known only to the kafka client and the authorization server.
52+
* `scope` - Kafka clients use this to specify the scope of the access request to the broker.
53+
* `sasl_oauthbearer_extensions` - Additional information to be provided to the kafka broker.
54+
* `https_ca_pem` - The CA certificates as a PEM string.
4955

5056
### Security
5157

docs/data-sources/stream_connections.md

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

5555
### Authentication
5656

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

6167
### Security
6268

docs/resources/stream_connection.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,38 @@ resource "mongodbatlas_stream_connection" "test" {
5555
}
5656
```
5757

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

6092
```terraform

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://your-domain.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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ const (
1717
dummyProjectID = "111111111111111111111111"
1818
instanceName = "InstanceName"
1919
authMechanism = "PLAIN"
20+
authMechanismOAuth = "OAUTHBEARER"
2021
authUsername = "user1"
22+
clientId = "auth0Client"
23+
clientSecret = "secret"
24+
tokenEndpointUrl = "https://your-domain.com/oauth2/token"
25+
scope = "read:messages write:messages"
26+
saslOauthbearerExtentions = "logicalCluster=cluster-kmo17m,identityPoolId=pool-l7Arl"
27+
httpsCaPem = "MHWER3343"
2128
securityProtocol = "SASL_SSL"
2229
bootstrapServers = "localhost:9092,another.host:9092"
2330
dbRole = "customRole"
@@ -50,6 +57,7 @@ type sdkToTFModelTestCase struct {
5057

5158
func TestStreamConnectionSDKToTFModel(t *testing.T) {
5259
var authConfigWithPasswordDefined = tfAuthenticationObject(t, authMechanism, authUsername, "raw password")
60+
var authConfigWithOAuth = tfAuthenticationObjectForOAuth(t, authMechanismOAuth, clientId, clientSecret, tokenEndpointUrl, scope, saslOauthbearerExtentions, httpsCaPem)
5361

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

645+
func tfAuthenticationObjectForOAuth(t *testing.T, mechanism, clientId, clientSecret, tokenEndpointUrl, scope, saslOauthbearerExtensions, httpsCaPem string) types.Object {
646+
t.Helper()
647+
auth, diags := types.ObjectValueFrom(t.Context(), streamconnection.ConnectionAuthenticationObjectType.AttrTypes, streamconnection.TFConnectionAuthenticationModel{
648+
Mechanism: types.StringValue(mechanism),
649+
ClientId: types.StringValue(clientId),
650+
ClientSecret: types.StringValue(clientSecret),
651+
TokenEndpointUrl: types.StringValue(tokenEndpointUrl),
652+
Scope: types.StringValue(scope),
653+
SaslOauthbearerExtensions: types.StringValue(saslOauthbearerExtensions),
654+
HttpsCaPem: types.StringValue(httpsCaPem),
655+
})
656+
if diags.HasError() {
657+
t.Errorf("failed to create terraform data model: %s", diags.Errors()[0].Summary())
658+
}
659+
return auth
660+
}
661+
599662
func tfAuthenticationObjectWithNoPassword(t *testing.T, mechanism, username string) types.Object {
600663
t.Helper()
601664
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)