Skip to content

Commit e830afc

Browse files
wz-gsaOpenCode Agent
andcommitted
refactor(oracle19c): DRY/inheritance trim of TLS work per follow-up review (#519,#538)
Follow-up DRY / over-engineering review (compare to how pg/mysql inherit crypto from AWS; don't build machinery we don't need). Core stays — Oracle genuinely requires the SSL option group + TCPS 2484 (unlike pg/mysql same-port TLS), and the cipher pin is required (the AWS 19c default cipher is documented FedRAMP-No, so we must override). Trimmed the extras: - H4: dropped the hardcoded SSL_SERVER_CERT_DN from the binding (uri/jdbcUrl descriptor + the ssl_server_cert_dn key). pg/mysql publish no DN; the Oracle driver verifies server identity against the CA bundle + ssl_server_dn_match. The DN was Amazon-owned + would break on cert rotation, and (M1) was unverified/ guessed. Kept ssl_required + ssl_server_dn_match + ca_cert_bundle_url. - Q5 (DRY): extracted ensureOptionGroupWithOptions (describe→create-if-absent→ modify); both ProvisionBaselineOptionGroup and ProvisionOrModifyCustomOptionGroup now call it, removing the byte-identical CreateOptionGroupInput literal + ModifyOptionGroup call. - S1: options.yml is our own committed file, not tenant input — moved the FedRAMP value-assertions (TLS==1.2, FIPS on, cipher allowlist) from a runtime guard to a CI test (TestOracleSSLBaselineIsFedRAMPCompliant), matching how pg/mysql have no runtime param guard. Kept a cheap runtime cipher/CA COMPATIBILITY guard on the provision path (option-b: oracleSSLCipherCACompatible) so a future ECDSA-on-RSA edit fails before the opaque AWS associate error; removed sortedKeys. - Docs (connection-examples/binding/hardening-baseline): removed the ssl_server_cert_dn references to match. Tests: TestOracleSSLBaselineIsFedRAMPCompliant (CI gate for the values), TestOracleSSLCipherCACompatible (runtime guard), TestGetCredentials oracle-se2 updated (no cert DN). go test ./... + cmd/tasks green; go vet + pre-commit green. Co-authored-by: OpenCode Agent <agent@gsa.gov>
1 parent 727f50a commit e830afc

8 files changed

Lines changed: 150 additions & 188 deletions

File tree

docs/oracle19c/binding.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ binding payload into `VCAP_SERVICES` (keys documented in
1212
`uri` (oracle scheme, TCPS `DESCRIPTION`), `jdbcUrl` (thin, TCPS `DESCRIPTION`),
1313
`username`, `password`, `host`, `port` (`2484`, TCPS), `protocol` (`tcps`),
1414
`service_name`/`sid` (`ORCL`), `db_name`, `name`, plus the TLS keys `ssl_required=true`,
15-
`ssl_server_dn_match=true`, `ssl_server_cert_dn` (the RDS server cert DN), and
16-
`ca_cert_bundle_url` (the GovCloud RDS CA bundle the client must trust). The `uri`
17-
and `jdbcUrl` use the full Oracle connect `DESCRIPTION` form (`PROTOCOL=TCPS`,
18-
`PORT=2484`, `SSL_SERVER_CERT_DN`) — EZConnect cannot express TCPS. No admin/master
15+
`ssl_server_dn_match=true`, and `ca_cert_bundle_url` (the GovCloud RDS CA bundle the
16+
client must trust; the driver verifies the server cert against it — no hardcoded DN
17+
is published). The `uri` and `jdbcUrl` use the full Oracle connect `DESCRIPTION` form
18+
(`PROTOCOL=TCPS`, `PORT=2484`) — EZConnect cannot express TCPS. No admin/master
1919
marker keys are present (asserted by `TestOracleBindingDoesNotLeakAdminMarkers`).
2020

2121
> **Encryption in transit (TLS).** The broker provisions an RDS Oracle SSL option

docs/oracle19c/connection-examples.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ After `cf bind-service my-app my-oracle`, the binding credentials appear in
88

99
```json
1010
{
11-
"uri": "oracle://APP_USER:REDACTED@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=my-oracle.abc.us-gov-west-1.rds.amazonaws.com)(PORT=2484))(CONNECT_DATA=(SID=ORCL))(SECURITY=(SSL_SERVER_CERT_DN=\"C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=my-oracle.abc.us-gov-west-1.rds.amazonaws.com\")))",
12-
"jdbcUrl": "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=my-oracle.abc.us-gov-west-1.rds.amazonaws.com)(PORT=2484))(CONNECT_DATA=(SID=ORCL))(SECURITY=(SSL_SERVER_CERT_DN=\"C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=my-oracle.abc.us-gov-west-1.rds.amazonaws.com\")))",
11+
"uri": "oracle://APP_USER:REDACTED@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=my-oracle.abc.us-gov-west-1.rds.amazonaws.com)(PORT=2484))(CONNECT_DATA=(SID=ORCL)))",
12+
"jdbcUrl": "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=my-oracle.abc.us-gov-west-1.rds.amazonaws.com)(PORT=2484))(CONNECT_DATA=(SID=ORCL)))",
1313
"username": "APP_USER",
1414
"password": "REDACTED",
1515
"host": "my-oracle.abc.us-gov-west-1.rds.amazonaws.com",
@@ -21,19 +21,20 @@ After `cf bind-service my-app my-oracle`, the binding credentials appear in
2121
"name": "ORCL",
2222
"ssl_required": "true",
2323
"ssl_server_dn_match": "true",
24-
"ssl_server_cert_dn": "C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=my-oracle.abc.us-gov-west-1.rds.amazonaws.com",
2524
"ca_cert_bundle_url": "https://truststore.pki.us-gov-west-1.rds.amazonaws.com/global/global-bundle.pem"
2625
}
2726
```
2827

2928
The binding advertises TLS/TCPS: `port` `2484`, `protocol` `tcps`, and a full
3029
Oracle connect `DESCRIPTION` (not EZConnect) in both `uri` and `jdbcUrl` carrying
31-
`PROTOCOL=TCPS` and `SSL_SERVER_CERT_DN`. `ssl_required=true` and
32-
`ssl_server_dn_match=true` instruct the client to encrypt **and** verify the server
33-
identity; `ca_cert_bundle_url` is the GovCloud RDS CA bundle the client must trust.
34-
The instance uses the RDS default RSA CA (`rds-ca-rsa2048-g1`), compatible with the
35-
ECDHE_RSA cipher. Verified offline + by go unit tests; not yet exercised against a
36-
live GovCloud RDS TLS handshake (WS15).
30+
`PROTOCOL=TCPS`. `ssl_required=true` and `ssl_server_dn_match=true` instruct the
31+
client to encrypt **and** verify the server identity; the client verifies that
32+
identity against `ca_cert_bundle_url` (the GovCloud RDS CA bundle it must trust) —
33+
the broker does **not** publish a hardcoded server-cert DN (the RDS cert subject is
34+
Amazon-owned and rotates; the driver derives it from the trusted cert). The instance
35+
uses the RDS default RSA CA (`rds-ca-rsa2048-g1`), compatible with the ECDHE_RSA
36+
cipher. Verified offline + by go unit tests; not yet exercised against a live
37+
GovCloud RDS TLS handshake (WS15).
3738

3839
> **TLS not yet reachable end-to-end (platform SG, [#541](https://github.com/cloud-gov/aws-broker/issues/541)).**
3940
> The broker provisions the RDS Oracle SSL option group and the binding expresses
@@ -101,7 +102,7 @@ keytool -importcert -alias rds-ca -file global-bundle.pem \
101102
```java
102103
String url = System.getenv("ORACLE_JDBC_URL"); // the TCPS jdbcUrl from the binding
103104
// jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=..)(PORT=2484))
104-
// (CONNECT_DATA=(SID=ORCL))(SECURITY=(SSL_SERVER_CERT_DN="...")))
105+
// (CONNECT_DATA=(SID=ORCL)))
105106
Properties p = new Properties();
106107
p.put("user", username);
107108
p.put("password", password);
@@ -148,8 +149,8 @@ provisions and attaches at create, which serves TLS on a dedicated **TCPS listen
148149
(port 2484)**: TLS 1.2, cipher `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`, and
149150
`FIPS.SSLFIPS_140=TRUE` (SC-8 / SC-8(1) / SC-13; DISA Oracle 19c STIG V-270579,
150151
V-270571). The binding advertises this posture (`port=2484`, `protocol=tcps`,
151-
`ssl_required=true`, `ssl_server_dn_match=true`, `ssl_server_cert_dn`,
152-
`ca_cert_bundle_url`). RDS Oracle SSL supports **TLS 1.2 only** (no 1.3) — acceptable
152+
`ssl_required=true`, `ssl_server_dn_match=true`, `ca_cert_bundle_url`). RDS Oracle
153+
SSL supports **TLS 1.2 only** (no 1.3) — acceptable
153154
for FedRAMP Moderate. Configure your driver to trust the GovCloud RDS CA bundle and
154155
require server-DN matching (examples above).
155156

docs/oracle19c/hardening-baseline.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ cipher (an ECDSA-only suite would need the ECC CA and is rejected fail-closed be
6969
the AWS call). `FIPS.SSLFIPS_140=TRUE` means clients **must** negotiate a
7070
FIPS/FedRAMP cipher or the handshake fails. The binding advertises the TCPS/2484
7171
posture (`port=2484`, `protocol=tcps`, `ssl_required=true`, `ssl_server_dn_match`,
72-
`ssl_server_cert_dn`, `ca_cert_bundle_url`).
72+
`ca_cert_bundle_url`); the client verifies the server cert against the CA bundle
73+
(no hardcoded DN is published).
7374

7475
> **TLS-only posture depends on the platform SG ([#541](https://github.com/cloud-gov/aws-broker/issues/541)).**
7576
> The broker provisions + attaches the SSL option and expresses the 2484 intent, but

services/rds/baselines_test.go

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -260,66 +260,54 @@ func indexFold(s, substr string) int {
260260
return -1
261261
}
262262

263-
func TestValidateOracleSSLSecurity(t *testing.T) {
264-
base := func() *oracleOptionsFile {
265-
return &oracleOptionsFile{
266-
CACertFamily: "rsa",
267-
Options: []oracleOption{{
268-
Name: "SSL", Port: 2484,
269-
Settings: []oracleOptionSetting{
270-
{Name: "SQLNET.SSL_VERSION", Value: "1.2"},
271-
{Name: "SQLNET.CIPHER_SUITE", Value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
272-
{Name: "FIPS.SSLFIPS_140", Value: "TRUE"},
273-
},
274-
}},
275-
}
276-
}
277-
if err := validateOracleSSLSecurity(base()); err != nil {
278-
t.Fatalf("valid baseline rejected: %v", err)
263+
// TestOracleSSLBaselineIsFedRAMPCompliant is the CI gate for the SSL security
264+
// invariants (S1: options.yml is our own committed file, so the values are enforced
265+
// by this test rather than a runtime guard — matching how pg/mysql have no runtime
266+
// param guard). It asserts the SHIPPED baseline is TLS 1.2 + FIPS on + a FedRAMP/FIPS
267+
// allowlisted cipher; a weakened options.yml fails go test and never merges.
268+
func TestOracleSSLBaselineIsFedRAMPCompliant(t *testing.T) {
269+
f, err := loadOracleOptions()
270+
if err != nil {
271+
t.Fatal(err)
279272
}
273+
s := f.sslSettings()
280274

281-
mutate := func(fn func(m map[string]string)) *oracleOptionsFile {
282-
f := base()
283-
m := map[string]string{}
284-
for _, s := range f.Options[0].Settings {
285-
m[s.Name] = s.Value
286-
}
287-
fn(m)
288-
f.Options[0].Settings = nil
289-
for k, v := range m {
290-
if v == "__delete__" {
291-
continue
292-
}
293-
f.Options[0].Settings = append(f.Options[0].Settings, oracleOptionSetting{Name: k, Value: v})
294-
}
295-
return f
275+
if v := s["SQLNET.SSL_VERSION"]; v != "1.2" {
276+
t.Errorf("SQLNET.SSL_VERSION=%q, require exactly \"1.2\" (FedRAMP floor; RDS Oracle ceiling)", v)
296277
}
297-
298-
bad := map[string]func(map[string]string){
299-
"tls 1.0 rejected": func(m map[string]string) { m["SQLNET.SSL_VERSION"] = "1.0" },
300-
"tls 1.1 rejected": func(m map[string]string) { m["SQLNET.SSL_VERSION"] = "1.1" },
301-
"missing version rejected": func(m map[string]string) { m["SQLNET.SSL_VERSION"] = "__delete__" },
302-
"fips off rejected": func(m map[string]string) { m["FIPS.SSLFIPS_140"] = "FALSE" },
303-
"fips missing rejected": func(m map[string]string) { m["FIPS.SSLFIPS_140"] = "__delete__" },
304-
"weak cipher rejected": func(m map[string]string) { m["SQLNET.CIPHER_SUITE"] = "TLS_RSA_WITH_AES_128_CBC_SHA" },
305-
"3des cipher rejected": func(m map[string]string) { m["SQLNET.CIPHER_SUITE"] = "TLS_RSA_WITH_3DES_EDE_CBC_SHA" },
306-
"missing cipher rejected": func(m map[string]string) { m["SQLNET.CIPHER_SUITE"] = "__delete__" },
307-
"ecdsa on rsa ca rejected": func(m map[string]string) { m["SQLNET.CIPHER_SUITE"] = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
308-
}
309-
for name, fn := range bad {
310-
t.Run(name, func(t *testing.T) {
311-
if err := validateOracleSSLSecurity(mutate(fn)); err == nil {
312-
t.Errorf("expected fail-closed rejection, got nil")
313-
}
314-
})
278+
if v := s["FIPS.SSLFIPS_140"]; !strings.EqualFold(v, "TRUE") {
279+
t.Errorf("FIPS.SSLFIPS_140=%q, require TRUE (SC-13)", v)
280+
}
281+
cipher, ok := s["SQLNET.CIPHER_SUITE"]
282+
if !ok || cipher == "" {
283+
t.Fatal("SQLNET.CIPHER_SUITE is required")
284+
}
285+
if _, ok := fedrampOracleSSLCiphers[cipher]; !ok {
286+
t.Errorf("cipher %q is not on the FedRAMP/FIPS allowlist", cipher)
315287
}
288+
}
316289

317-
// The shipped embedded baseline must pass the security guard.
318-
shipped, err := loadOracleOptions()
319-
if err != nil {
320-
t.Fatal(err)
290+
// TestOracleSSLCipherCACompatible covers the runtime compatibility guard kept on the
291+
// provision path (option-b): an ECDSA-only cipher on the RSA CA is rejected before
292+
// the AWS associate call.
293+
func TestOracleSSLCipherCACompatible(t *testing.T) {
294+
ok := &oracleOptionsFile{
295+
CACertFamily: "rsa",
296+
Options: []oracleOption{{Name: "SSL", Settings: []oracleOptionSetting{
297+
{Name: "SQLNET.CIPHER_SUITE", Value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"},
298+
}}},
299+
}
300+
if err := oracleSSLCipherCACompatible(ok); err != nil {
301+
t.Errorf("RSA cipher on RSA CA should be compatible, got %v", err)
321302
}
322-
if err := validateOracleSSLSecurity(shipped); err != nil {
323-
t.Errorf("shipped options.yml fails the SSL security guard: %v", err)
303+
304+
bad := &oracleOptionsFile{
305+
CACertFamily: "rsa",
306+
Options: []oracleOption{{Name: "SSL", Settings: []oracleOptionSetting{
307+
{Name: "SQLNET.CIPHER_SUITE", Value: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"},
308+
}}},
309+
}
310+
if err := oracleSSLCipherCACompatible(bad); err == nil {
311+
t.Error("ECDSA cipher on RSA CA must be rejected")
324312
}
325313
}

services/rds/credentials.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,14 @@ func (u *RDSCredentialUtils) getCredentials(i *RDSInstance, password string) (ma
127127

128128
// oracleCredentials builds the Oracle-specific binding payload (WS9 #528, TLS #538).
129129
// It returns machine-readable connection details for a bound app over TLS/TCPS:
130-
// the SSL port (2484), a TCPS connect descriptor (uri + jdbcUrl with PROTOCOL=TCPS
131-
// and SSL_SERVER_CERT_DN), ssl_server_dn_match, and the GovCloud RDS CA bundle URL
132-
// the client must trust. Like the postgres/mysql plans, the broker returns the
133-
// instance master credential per binding; the customer creates their own
134-
// least-privilege in-database users (the intended shared-responsibility boundary).
135-
// No admin/master marker key is exposed here.
130+
// the SSL port (2484), a TCPS connect descriptor (uri + jdbcUrl with PROTOCOL=TCPS),
131+
// ssl_server_dn_match, and the GovCloud RDS CA bundle URL the client must trust.
132+
// Server identity is verified by the driver against the CA bundle + DN match — the
133+
// broker does NOT publish a hardcoded cert DN (the RDS cert subject is Amazon-owned
134+
// and rotates; pinning it would duplicate the driver's own check and break on
135+
// rotation). Like the postgres/mysql plans, the broker returns the instance master
136+
// credential per binding; the customer creates their own least-privilege in-database
137+
// users (the intended shared-responsibility boundary). No admin/master marker key.
136138
//
137139
// FAIL-CLOSED (security review): if the TLS SSL port cannot be resolved from the
138140
// embedded baseline, this returns an error rather than falling back to the
@@ -154,17 +156,14 @@ func oracleCredentials(i *RDSInstance, password, scheme, serviceName string) (ma
154156
return nil, errors.New("oracle TLS binding: SSL port is 0, refusing to emit a binding that would claim TLS on a plaintext port")
155157
}
156158

157-
// Server cert DN for RDS Oracle TLS: the RDS-issued cert CN is the endpoint.
158-
// Enables ssl_server_dn_match to verify server identity (not just encrypt).
159-
certDN := fmt.Sprintf("C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=%s", i.Host)
160-
161-
// TCPS connect descriptor (URI form) with DN match — EZConnect cannot express
162-
// PROTOCOL=TCPS or the cert DN, so we use the full DESCRIPTION form.
159+
// TCPS connect descriptor (URI form) — EZConnect cannot express PROTOCOL=TCPS,
160+
// so we use the full DESCRIPTION form. Server identity is verified by the
161+
// client driver via ssl_server_dn_match + the CA bundle (below); we do not pin
162+
// the cert DN here.
163163
descriptor := fmt.Sprintf(
164164
"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=%s)(PORT=%d))"+
165-
"(CONNECT_DATA=(SID=%s))"+
166-
"(SECURITY=(SSL_SERVER_CERT_DN=\"%s\")))",
167-
i.Host, sslPort, serviceName, certDN,
165+
"(CONNECT_DATA=(SID=%s)))",
166+
i.Host, sslPort, serviceName,
168167
)
169168
uri := fmt.Sprintf("%s://%s:%s@%s", scheme, i.Username, password, descriptor)
170169
jdbcURL := fmt.Sprintf("jdbc:oracle:thin:@%s", descriptor)
@@ -183,7 +182,6 @@ func oracleCredentials(i *RDSInstance, password, scheme, serviceName string) (ma
183182
"name": serviceName,
184183
"ssl_required": "true",
185184
"ssl_server_dn_match": "true",
186-
"ssl_server_cert_dn": certDN,
187185
"ca_cert_bundle_url": "https://truststore.pki.us-gov-west-1.rds.amazonaws.com/global/global-bundle.pem",
188186
}, nil
189187
}

services/rds/credentials_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,11 @@ func TestGetCredentials(t *testing.T) {
110110
password: "fake-pw",
111111
// Oracle binding is TLS/TCPS on 2484 with a DESCRIPTION connect string
112112
// + DN match + GovCloud CA bundle (#538). Port is the SSL port, not 1521.
113+
// No hardcoded cert DN: the driver verifies identity via the CA bundle +
114+
// ssl_server_dn_match (DRY review — pg/mysql omit it, DN is Amazon-owned).
113115
expectedCreds: map[string]string{
114-
"uri": `oracle://user-1:fake-pw@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=host)(PORT=2484))(CONNECT_DATA=(SID=ORCL))(SECURITY=(SSL_SERVER_CERT_DN="C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=host")))`,
115-
"jdbcUrl": `jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=host)(PORT=2484))(CONNECT_DATA=(SID=ORCL))(SECURITY=(SSL_SERVER_CERT_DN="C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=host")))`,
116+
"uri": `oracle://user-1:fake-pw@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=host)(PORT=2484))(CONNECT_DATA=(SID=ORCL)))`,
117+
"jdbcUrl": `jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=host)(PORT=2484))(CONNECT_DATA=(SID=ORCL)))`,
116118
"username": "user-1",
117119
"password": "fake-pw",
118120
"host": "host",
@@ -124,7 +126,6 @@ func TestGetCredentials(t *testing.T) {
124126
"name": "ORCL",
125127
"ssl_required": "true",
126128
"ssl_server_dn_match": "true",
127-
"ssl_server_cert_dn": "C=US,ST=Washington,L=Seattle,O=Amazon.com,OU=RDS,CN=host",
128129
"ca_cert_bundle_url": "https://truststore.pki.us-gov-west-1.rds.amazonaws.com/global/global-bundle.pem",
129130
},
130131
},

0 commit comments

Comments
 (0)