Skip to content

Commit c3b4d28

Browse files
committed
Merge branch 'support-model-encryption' into 'release/4.2'
Support model encryption for initialize domain on PV See merge request weblogic-cloud/weblogic-kubernetes-operator!4921
2 parents 8bf8fea + fd0c06f commit c3b4d28

File tree

15 files changed

+293
-20
lines changed

15 files changed

+293
-20
lines changed

documentation/domains/Domain.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,10 @@
743743
"description": "An optional field that describes the configuration for creating a PersistentVolumeClaim for `Domain on PV`. PersistentVolumeClaim is a user\u0027s request for and claim to a persistent volume. The operator will perform this one-time create operation only if the persistent volume claim does not already exist. Omit this section if you have manually created a persistent volume claim. If specified, the name must match one of the volumes under `serverPod.volumes` and the domain home must reside in the mount path of the volume using this claim. More info: https://oracle.github.io/weblogic-kubernetes-operator/managing-domains/domain-on-pv-initialization#pvc",
744744
"$ref": "#/definitions/PersistentVolumeClaim"
745745
},
746+
"modelEncryptionPassphraseSecret": {
747+
"description": "Specifies the secret name of the WebLogic Deployment Tool encryption passphrase if the WDT models provided in the \u0027domainCreationImages\u0027 or \u0027domainCreationConfigMap\u0027 are encrypted using the WebLogic Deployment Tool \u0027encryptModel\u0027 command. The secret must use the key \u0027passphrase\u0027 containing the actual passphrase for encryption.",
748+
"type": "string"
749+
},
746750
"setDefaultSecurityContextFsGroup": {
747751
"description": "Specifies whether the operator will set the default \u0027fsGroup\u0027 in the introspector job pod security context. This is needed to create the domain home directory on PV in some environments. If the \u0027fsGroup\u0027 is specified as part of \u0027spec.introspector.serverPod.podSecurityContext\u0027, then the operator will use that \u0027fsGroup\u0027 instead of the default \u0027fsGroup\u0027. Defaults to true.",
748752
"type": "boolean"

documentation/domains/Domain.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ The current status of the operation of the WebLogic domain. Updated automaticall
244244
| Name | Type | Description |
245245
| --- | --- | --- |
246246
| `domain` | [Domain On PV](#domain-on-pv) | Describes the configuration for creating an initial WebLogic Domain in persistent volume (`Domain in PV`). The operator will not recreate or update the domain if it already exists. Required. |
247+
| `modelEncryptionPassphraseSecret` | string | Specifies the secret name of the WebLogic Deployment Tool encryption passphrase if the WDT models provided in the 'domainCreationImages' or 'domainCreationConfigMap' are encrypted using the WebLogic Deployment Tool 'encryptModel' command. The secret must use the key 'passphrase' containing the actual passphrase for encryption. |
247248
| `persistentVolume` | [Persistent Volume](#persistent-volume) | An optional field that describes the configuration to create a PersistentVolume for `Domain on PV` domain. Omit this section if you have manually created a persistent volume. The operator will perform this one-time create operation only if the persistent volume does not already exist. The operator will not recreate or update the PersistentVolume when it exists. More info: https://oracle.github.io/weblogic-kubernetes-operator/managing-domains/domain-on-pv-initialization#pv |
248249
| `persistentVolumeClaim` | [Persistent Volume Claim](#persistent-volume-claim) | An optional field that describes the configuration for creating a PersistentVolumeClaim for `Domain on PV`. PersistentVolumeClaim is a user's request for and claim to a persistent volume. The operator will perform this one-time create operation only if the persistent volume claim does not already exist. Omit this section if you have manually created a persistent volume claim. If specified, the name must match one of the volumes under `serverPod.volumes` and the domain home must reside in the mount path of the volume using this claim. More info: https://oracle.github.io/weblogic-kubernetes-operator/managing-domains/domain-on-pv-initialization#pvc |
249250
| `runDomainInitContainerAsRoot` | Boolean | Specifies whether the operator will run the domain initialization init container in the introspector job as root. This may be needed in some environments to create the domain home directory on PV. Defaults to false. |

documentation/site/content/managing-domains/domain-on-pv/usage.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ To use this feature, provide the following information:
2929
- [Domain information](#domain-information) - This describes the domain type and whether the operator should create the RCU schema.
3030
- [Domain WDT models](#domain-creation-models) - This is where the WDT Home, WDT model, WDT archive, and WDT variables files reside.
3131
- [Optional WDT models ConfigMap](#optional-wdt-models-configmap) - Optional, WDT model, WDT variables files.
32+
- [Using WDT model encryption](#using-wdt-model-encryption) - Optional, using WDT model encryption.
3233
- [Domain resource YAML file]({{< relref "/reference/domain-resource.md">}}) - This is for deploying the domain in WebLogic Kubernetes Operator.
3334

3435

@@ -113,6 +114,21 @@ those in `domainCreationImages`.
113114

114115
The files inside this ConfigMap must have file extensions, `.yaml`, `.properties`, or `.zip`.
115116

117+
#### Using WDT model encryption
118+
119+
Starting in WebLogic Kubernetes Operator version 4.2.16. If the provided WDT models are encrypted using the WDT `encryptModel`
120+
command. You can specify the encryption passphrase as a secret in the domain resource YAML. WDT will use the value in the
121+
secret to decrypt the models for domain creation.
122+
123+
```yaml
124+
initializeDomainOnPV:
125+
modelEncryptionPassphraseSecret: model-encryption-secret
126+
```
127+
128+
The secret must have a key `passphrase` containing the value of the WDT encryption passphrase used to encrypt the models.
129+
130+
`kubectl create secret generic model-encrypion-secret --from-literal=passphrase=<encryption passphrase value>`
131+
116132
#### Volumes and VolumeMounts information
117133

118134
You must provide the `volumes` and `volumeMounts` information in `domain.spec.serverPod`. This allows the pod to mount the persistent

integration-tests/src/test/java/oracle/weblogic/domain/InitializeDomainOnPV.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public class InitializeDomainOnPV {
5151
+ " If the 'fsGroup' is specified as part of 'spec.introspector.serverPod.podSecurityContext', then the operator"
5252
+ " will use that 'fsGroup' instead of the default 'fsGroup'. Defaults to true.")
5353
public Boolean setDefaultSecurityContextFsGroup;
54+
55+
@ApiModelProperty("Specifies the secret name of the WebLogic Deployment Tool encryption passphrase if the WDT models "
56+
+ "provided in the 'domainCreationImages' or 'domainCreationConfigMap' are encrypted using the "
57+
+ "WebLogic Deployment Tool 'encryptModel' command. "
58+
+ "The secret must use the key 'passphrase' containing the actual passphrase for encryption.")
59+
public String modelEncryptionPassphraseSecret;
60+
5461

5562
public PersistentVolume getPersistentVolume() {
5663
return persistentVolume;
@@ -105,6 +112,15 @@ public InitializeDomainOnPV setDefaultFsGroup(Boolean setDefaultFsGroup) {
105112
this.setDefaultSecurityContextFsGroup = setDefaultFsGroup;
106113
return this;
107114
}
115+
116+
public String getModelEncryptionPassphraseSecret() {
117+
return modelEncryptionPassphraseSecret;
118+
}
119+
120+
public InitializeDomainOnPV modelEncryptionPassphraseSecret(String modelEncryptionPassphraseSecret) {
121+
this.modelEncryptionPassphraseSecret = modelEncryptionPassphraseSecret;
122+
return this;
123+
}
108124

109125
@Override
110126
public String toString() {
@@ -138,12 +154,13 @@ public boolean equals(Object other) {
138154
}
139155

140156
InitializeDomainOnPV rhs = ((InitializeDomainOnPV) other);
141-
EqualsBuilder builder =
142-
new EqualsBuilder()
157+
EqualsBuilder builder
158+
= new EqualsBuilder()
143159
.append(persistentVolume, rhs.persistentVolume)
144160
.append(persistentVolumeClaim, rhs.persistentVolumeClaim)
145161
.append(domain, rhs.domain)
146-
.append(waitForPvcToBind, rhs.waitForPvcToBind);
162+
.append(waitForPvcToBind, rhs.waitForPvcToBind)
163+
.append(modelEncryptionPassphraseSecret, rhs.modelEncryptionPassphraseSecret);
147164

148165
return builder.isEquals();
149166
}

integration-tests/src/test/java/oracle/weblogic/kubernetes/ItSystemResOverrides.java

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
package oracle.weblogic.kubernetes;
55

66
import java.io.File;
7+
import java.io.FileOutputStream;
78
import java.io.IOException;
89
import java.net.http.HttpResponse;
10+
import java.nio.charset.StandardCharsets;
11+
import java.nio.file.Files;
912
import java.nio.file.Path;
1013
import java.nio.file.Paths;
1114
import java.time.OffsetDateTime;
@@ -15,7 +18,9 @@
1518
import java.util.LinkedHashMap;
1619
import java.util.List;
1720
import java.util.Map;
21+
import java.util.Properties;
1822
import java.util.concurrent.Callable;
23+
import java.util.stream.Collectors;
1924

2025
import io.kubernetes.client.custom.Quantity;
2126
import io.kubernetes.client.custom.V1Patch;
@@ -24,6 +29,7 @@
2429
import io.kubernetes.client.openapi.models.V1LocalObjectReference;
2530
import io.kubernetes.client.openapi.models.V1ObjectMeta;
2631
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource;
32+
import io.kubernetes.client.openapi.models.V1Secret;
2733
import io.kubernetes.client.openapi.models.V1Volume;
2834
import io.kubernetes.client.openapi.models.V1VolumeMount;
2935
import oracle.weblogic.domain.AdminServer;
@@ -38,6 +44,8 @@
3844
import oracle.weblogic.domain.DomainResource;
3945
import oracle.weblogic.domain.DomainSpec;
4046
import oracle.weblogic.domain.ServerPod;
47+
import oracle.weblogic.kubernetes.actions.impl.primitive.Command;
48+
import oracle.weblogic.kubernetes.actions.impl.primitive.CommandParams;
4149
import oracle.weblogic.kubernetes.actions.impl.primitive.WitParams;
4250
import oracle.weblogic.kubernetes.annotations.IntegrationTest;
4351
import oracle.weblogic.kubernetes.annotations.Namespaces;
@@ -59,19 +67,25 @@
5967
import static oracle.weblogic.kubernetes.TestConstants.IMAGE_PULL_POLICY;
6068
import static oracle.weblogic.kubernetes.TestConstants.K8S_NODEPORT_HOST;
6169
import static oracle.weblogic.kubernetes.TestConstants.OKE_CLUSTER;
70+
import static oracle.weblogic.kubernetes.TestConstants.RESULTS_ROOT;
71+
import static oracle.weblogic.kubernetes.TestConstants.RESULTS_TEMPFILE;
6272
import static oracle.weblogic.kubernetes.TestConstants.TRAEFIK_INGRESS_HTTP_HOSTPORT;
6373
import static oracle.weblogic.kubernetes.TestConstants.WEBLOGIC_IMAGE_TAG_DEFAULT;
6474
import static oracle.weblogic.kubernetes.TestConstants.WEBLOGIC_IMAGE_TO_USE_IN_SPEC;
6575
import static oracle.weblogic.kubernetes.actions.ActionConstants.APP_DIR;
76+
import static oracle.weblogic.kubernetes.actions.ActionConstants.DOWNLOAD_DIR;
6677
import static oracle.weblogic.kubernetes.actions.ActionConstants.MODEL_DIR;
6778
import static oracle.weblogic.kubernetes.actions.ActionConstants.RESOURCE_DIR;
79+
import static oracle.weblogic.kubernetes.actions.ActionConstants.WDT_DOWNLOAD_URL;
80+
import static oracle.weblogic.kubernetes.actions.TestActions.createSecret;
6881
import static oracle.weblogic.kubernetes.actions.TestActions.getNextIntrospectVersion;
6982
import static oracle.weblogic.kubernetes.actions.TestActions.getServiceNodePort;
7083
import static oracle.weblogic.kubernetes.actions.TestActions.getServicePort;
7184
import static oracle.weblogic.kubernetes.actions.TestActions.shutdownDomain;
7285
import static oracle.weblogic.kubernetes.actions.TestActions.startDomain;
7386
import static oracle.weblogic.kubernetes.actions.impl.Domain.patchDomainCustomResource;
7487
import static oracle.weblogic.kubernetes.assertions.TestAssertions.podStateNotChanged;
88+
import static oracle.weblogic.kubernetes.assertions.TestAssertions.secretExists;
7589
import static oracle.weblogic.kubernetes.utils.ApplicationUtils.verifyAdminServerRESTAccess;
7690
import static oracle.weblogic.kubernetes.utils.AuxiliaryImageUtils.createAndPushAuxiliaryImage;
7791
import static oracle.weblogic.kubernetes.utils.BuildApplication.buildApplication;
@@ -91,7 +105,6 @@
91105
import static oracle.weblogic.kubernetes.utils.ConfigMapUtils.createConfigMapFromFiles;
92106
import static oracle.weblogic.kubernetes.utils.DeployUtil.deployUsingWlst;
93107
import static oracle.weblogic.kubernetes.utils.DomainUtils.createDomainAndVerify;
94-
import static oracle.weblogic.kubernetes.utils.FileUtils.createWdtPropertyFile;
95108
import static oracle.weblogic.kubernetes.utils.FmwUtils.getConfiguration;
96109
import static oracle.weblogic.kubernetes.utils.ImageUtils.createBaseRepoSecret;
97110
import static oracle.weblogic.kubernetes.utils.JobUtils.createDomainJob;
@@ -152,6 +165,10 @@ class ItSystemResOverrides {
152165
static Path sitconfigAppPath;
153166
String overridecm = "configoverride-cm";
154167
LinkedHashMap<String, OffsetDateTime> podTimestamps;
168+
169+
private static Path encryptModelScript;
170+
private static final String passPhrase = "encryptPA55word";
171+
private static final String encryptionSecret = "model-encryption-secret";
155172

156173
private static LoggingFacade logger = null;
157174

@@ -166,7 +183,7 @@ class ItSystemResOverrides {
166183
* @param namespaces injected by JUnit
167184
*/
168185
@BeforeAll
169-
public void initAll(@Namespaces(2) List<String> namespaces) {
186+
public void initAll(@Namespaces(2) List<String> namespaces) throws IOException {
170187
logger = getLogger();
171188

172189
logger.info("Assign a unique namespace for operator");
@@ -175,6 +192,9 @@ public void initAll(@Namespaces(2) List<String> namespaces) {
175192
logger.info("Assign a unique namespace for domain namspace");
176193
assertNotNull(namespaces.get(1), "Namespace is null");
177194
domainNamespace = namespaces.get(1);
195+
196+
logger.info("installing WebLogic Deploy Tool");
197+
downloadAndInstallWDT();
178198

179199
// install operator and verify its running in ready state
180200
installAndVerifyOperator(opNamespace, domainNamespace);
@@ -391,7 +411,7 @@ private void verifyIntrospectorRuns() {
391411
}
392412

393413
//create a standard WebLogic domain.
394-
private void createDomain() {
414+
private void createDomain() throws IOException {
395415
String uniqueDomainHome = "/shared/" + domainNamespace + "/domains/";
396416

397417
// create pull secrets for WebLogic image when running in non Kind Kubernetes cluster
@@ -404,8 +424,17 @@ private void createDomain() {
404424
final String wlsModelFilePrefix = "sitconfig-dci-model";
405425
final String wlsModelFile = wlsModelFilePrefix + ".yaml";
406426
t3ChannelPort = getNextFreePort();
427+
logger.info("Create WDT property file");
407428
File wlsModelPropFile = createWdtPropertyFile(wlsModelFilePrefix,
408429
K8S_NODEPORT_HOST, t3ChannelPort);
430+
logger.info("Create WDT passphrase file");
431+
File passphraseFile = createPassphraseFile(passPhrase);
432+
logger.info("Run encruptModel.sh script to encrypt clear text password in property file");
433+
encryptModel(encryptModelScript,
434+
Path.of(MODEL_DIR, wlsModelFile),
435+
wlsModelPropFile.toPath(), passphraseFile.toPath());
436+
createSecretWithUsernamePassword(wlSecretName, opNamespace, clusterName, passPhrase);
437+
createEncryptionSecret(encryptionSecret, domainNamespace);
409438

410439
// create domainCreationImage
411440
String domainCreationImageName = DOMAIN_IMAGES_PREFIX + "wls-domain-on-pv-image";
@@ -437,10 +466,12 @@ private void createDomain() {
437466
configuration = getConfiguration(pvName, pvcName, pvCapacity, pvcRequest, storageClassName,
438467
ItSystemResOverrides.class.getSimpleName());
439468
}
440-
configuration.getInitializeDomainOnPV().domain(new DomainOnPV()
441-
.createMode(CreateIfNotExists.DOMAIN)
442-
.domainCreationImages(Collections.singletonList(domainCreationImage))
443-
.domainType(DomainOnPVType.WLS));
469+
configuration.getInitializeDomainOnPV()
470+
.modelEncryptionPassphraseSecret(encryptionSecret)
471+
.domain(new DomainOnPV()
472+
.createMode(CreateIfNotExists.DOMAIN)
473+
.domainCreationImages(Collections.singletonList(domainCreationImage))
474+
.domainType(DomainOnPVType.WLS));
444475
configuration.overrideDistributionStrategy("Dynamic");
445476

446477
// create secrets
@@ -628,4 +659,82 @@ private void restartDomain() {
628659
checkPodReadyAndServiceExists(managedServerPodNamePrefix + i, domainUid, domainNamespace);
629660
}
630661
}
662+
663+
public static File createWdtPropertyFile(String wlsModelFilePrefix, String nodePortHost, int t3Port) {
664+
// create property file used with domain model file
665+
Properties p = new Properties();
666+
p.setProperty("WebLogicAdminUserName", ADMIN_USERNAME_DEFAULT);
667+
p.setProperty("WebLogicAdminPassword", ADMIN_PASSWORD_DEFAULT);
668+
p.setProperty("K8S_NODEPORT_HOST", nodePortHost);
669+
p.setProperty("T3_CHANNEL_PORT", Integer.toString(t3Port));
670+
671+
// create a model property file
672+
File domainPropertiesFile = assertDoesNotThrow(() ->
673+
File.createTempFile(wlsModelFilePrefix, ".properties", new File(RESULTS_TEMPFILE)),
674+
"Failed to create WLS model properties file");
675+
676+
// create the property file
677+
assertDoesNotThrow(() ->
678+
p.store(new FileOutputStream(domainPropertiesFile), "WLS properties file"),
679+
"Failed to write WLS properties file");
680+
681+
return domainPropertiesFile;
682+
}
683+
684+
private static void downloadAndInstallWDT() throws IOException {
685+
String wdtUrl = WDT_DOWNLOAD_URL + "/download/weblogic-deploy.zip";
686+
Path destLocation = Path.of(DOWNLOAD_DIR, "wdt", "weblogic-deploy.zip");
687+
encryptModelScript = Path.of(DOWNLOAD_DIR, "wdt", "weblogic-deploy", "bin", "encryptModel.sh");
688+
if (!Files.exists(destLocation) && !Files.exists(encryptModelScript)) {
689+
logger.info("Downloading WDT to {0}", destLocation);
690+
Files.createDirectories(destLocation.getParent());
691+
OracleHttpClient.downloadFile(wdtUrl, destLocation.toString(), null, null, 3);
692+
String cmd = "cd " + destLocation.getParent() + ";unzip " + destLocation;
693+
assertTrue(Command.withParams(new CommandParams().command(cmd)).execute(), "unzip command failed");
694+
}
695+
assertTrue(Files.exists(encryptModelScript), "could not find createDomain.sh script");
696+
}
697+
698+
private static File createPassphraseFile(String passPhrase) throws IOException {
699+
// create pass phrase file used with domain model file
700+
File passphraseFile = assertDoesNotThrow(()
701+
-> File.createTempFile("passphrase", ".txt", new File(RESULTS_TEMPFILE)),
702+
"Failed to create WLS model encrypt passphrase file");
703+
Files.write(passphraseFile.toPath(), passPhrase.getBytes(StandardCharsets.UTF_8));
704+
logger.info("passphrase file contents {0}", Files.readString(passphraseFile.toPath()));
705+
return passphraseFile;
706+
}
707+
708+
private static void encryptModel(Path encryptModelScript, Path modelFile,
709+
Path propertyFile, Path passphraseFile) throws IOException {
710+
Path mwHome = Path.of(RESULTS_ROOT, "mwhome");
711+
logger.info("Encrypting property file containing the secrets {0}", propertyFile.toString());
712+
List<String> command = List.of(
713+
encryptModelScript.toString(),
714+
"-oracle_home", mwHome.toString(),
715+
"-model_file", modelFile.toString(),
716+
"-variable_file", propertyFile.toString(),
717+
"-passphrase_file", passphraseFile.toString()
718+
);
719+
logger.info("running {0}", command);
720+
assertTrue(Command.withParams(new CommandParams()
721+
.command(command.stream().collect(Collectors.joining(" ")))).execute(),
722+
"encryptModel.sh command failed");
723+
logger.info("Encrypted passphrase file contents {0}", Files.readString(propertyFile));
724+
}
725+
726+
public static void createEncryptionSecret(String secretName, String namespace) {
727+
Map<String, String> secretMap = new HashMap<>();
728+
secretMap.put("passphrase", passPhrase);
729+
730+
if (!secretExists(secretName, namespace)) {
731+
boolean secretCreated = assertDoesNotThrow(() -> createSecret(new V1Secret()
732+
.metadata(new V1ObjectMeta()
733+
.name(secretName)
734+
.namespace(namespace))
735+
.stringData(secretMap)), "Create secret failed with ApiException");
736+
737+
assertTrue(secretCreated, String.format("create secret failed for %s", secretName));
738+
}
739+
}
631740
}

0 commit comments

Comments
 (0)