Skip to content
Open
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to the Zowe Installer will be documented in this file.

## `3.4.0`

- Enhancement: Support added for IPv6 addresses within the zowe.externalDomains and zowe.server.listenAddresses arrays. [#4346](https://github.com/zowe/zowe-install-packaging/pull/4346)
- Enhancement: Support added for IPv6 addresses within APIML static definition files [#4346](https://github.com/zowe/zowe-install-packaging/pull/4346)
- Enhancement: Detect if `SDSF` is available. [#4389](https://github.com/zowe/zowe-install-packaging/pull/4389)
- Bugfix: internal routine `copy_to_data_set` did not correctly check if data set exists. [#4476](https://github.com/zowe/zowe-install-packaging/pull/4476)
- Enhancement: `ZWEGEN00` supports long path by continuation character, which can be used in JCL `ZWEGENER` [#4459](https://github.com/zowe/zowe-install-packaging/pull/4459)
Expand Down
3 changes: 3 additions & 0 deletions bin/libs/certificate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ pkcs12_create_certificate_and_sign() {
# test if it's IP
if expr "${item}" : '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' >/dev/null; then
san="${san}ip:${item},"
elif expr "${item}" : '.*\:.*' > /dev/null; then
# this does not work on java 8, but does on java 17.
san="${san}ip:[${item}],"
else
san="${san}dns:${item},"
fi
Expand Down
35 changes: 33 additions & 2 deletions bin/libs/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as configmgr from './configmgr';
import * as varlib from './var';
import * as fakejq from './fakejq';
import * as configUtils from './config';
import * as network from './network';

const CONFIG_MGR=configmgr.CONFIG_MGR;
const ZOWE_CONFIG=configmgr.getZoweConfig();
Expand Down Expand Up @@ -494,7 +495,37 @@ export function processComponentApimlStaticDefinitions(componentDir: string): bo
std.setenv('ZOSMF_NON_SECURE_PORT_ENABLED', `${nonSecurePortEnabled}`);
std.setenv('ZOSMF_SECURE_PORT_ENABLED', `${securePortEnabled}`);

const resolvedContents = varlib.resolveShellTemplate(contents);
let resolvedContents = varlib.resolveShellTemplate(contents);
let rest = resolvedContents;
let ipv6ResolvedContents = '';

let urlRegexp = new RegExp(/\w+:\/\/\S+/);
let whitespaceRegexp = new RegExp(/\s+/);
let indexOfUrl = rest.search(urlRegexp);
//no urls, no change
if (indexOfUrl == -1) { ipv6ResolvedContents = resolvedContents; }

// env vars such as ZWE_haInstance_hostname or ZWE_zowe_externalDomains_0 may need to be wrapped in "[]"
// if they are ipv6 strings, as noted in RFC 3986
while (indexOfUrl != -1) {
let beginning = rest.substring(0, indexOfUrl);
rest = rest.substring(indexOfUrl);
let stopIndex = rest.search(whitespaceRegexp);
if (stopIndex == -1) { stopIndex = undefined; }

let urlString = rest.substring(0, stopIndex);
let wrappedUrlString = network.wrapIpv6Url(urlString);

ipv6ResolvedContents += beginning + wrappedUrlString;

if (stopIndex) {
rest = rest.substring(stopIndex);
indexOfUrl = rest.search(urlRegexp);
if (indexOfUrl == -1) {
ipv6ResolvedContents += rest;
}
} else { break; }
}


//discovery static code requires specifically .yml. Not .yaml
Expand All @@ -508,7 +539,7 @@ export function processComponentApimlStaticDefinitions(componentDir: string): bo
let errorObj;
let fileReturn = std.open(outPath, 'w', errorObj);
if (fileReturn && !errorObj) {
fileReturn.puts(resolvedContents);
fileReturn.puts(ipv6ResolvedContents);
fileReturn.close();
shell.execSync(`chmod`, `770`, outPath);
} else {
Expand Down
6 changes: 4 additions & 2 deletions bin/libs/configmgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ function getDiscoveryServiceUrlHa(config) {
std.exit(1);
}

const url = `https://${haInstance.hostname}:${port}/eureka/`;
//Accomodate ipv6 in hostname in url by wrapping in '[ ]'
const url = `https://${haInstance.hostname.includes(':') ? '['+haInstance.hostname+']' : haInstance.hostname}:${port}/eureka/`;

if (list.includes(url)) {
console.log(`Warn: Multiple haInstances reffers to the same hostname: ${haInstance.hostname}`);
Expand All @@ -176,7 +177,8 @@ function getDiscoveryServiceUrlNonHa(config) {
}

config.zowe.externalDomains.forEach((domain: string) => {
const url = `https://${domain}:${port}/eureka/`;
//Accomodate ipv6 in hostname in url by wrapping in '[ ]'
const url = `https://${domain.includes(':') ? '['+domain+']' : domain}:${port}/eureka/`;
if (list.includes(url)) {
console.log(`Warn: External domains are not unique: ${domain}`);
} else {
Expand Down
51 changes: 51 additions & 0 deletions bin/libs/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,54 @@ export function getIpAddress(hostname: string): string|undefined {

return ip;
}


/*
This function wraps the ipv6 portion of a url in square brackets []
*/
export function wrapIpv6Url(urlString: string): string {
let slashIndex = urlString.indexOf('://');
if (slashIndex == -1) {
// not url
return urlString;
}
let colonIndex = urlString.indexOf(':', slashIndex+1);
if (colonIndex == -1) {
// not ipv6
return urlString;
}
let bracketIndex = urlString.indexOf('[', slashIndex+1);
if (bracketIndex != -1 && (bracketIndex < colonIndex)) {
// string already in format of foo://[thing: ...
// no need to convert
return urlString;
}

let endSlashIndex = urlString.indexOf('/', slashIndex+3);
let path = '';
if (endSlashIndex == -1) {
endSlashIndex = urlString.length;
} else {
path = urlString.substring(endSlashIndex);
}

let hostnameAndPortSection = urlString.substring(slashIndex+3, endSlashIndex);
let lastColonIndex = hostnameAndPortSection.lastIndexOf(':');
if (lastColonIndex == -1) {
// no ipv6 spotted because no colon spotted at all
return urlString;
}
if (hostnameAndPortSection.indexOf(':') == lastColonIndex) {
// no ipv6 spotted because only colon spotted is port separator
return urlString;
}
let port = hostnameAndPortSection.substring(lastColonIndex+1);

let beginning = urlString.substring(0, slashIndex+3);
if (Number.isNaN(Number(port))) {
//format like https://::ffff:127.0.0.1/ seen - end is not a port, no port, just wrap.
return beginning + '[' + hostnameAndPortSection.substring(0, lastColonIndex) + port + ']' + path;
} else {
return beginning + '[' + hostnameAndPortSection.substring(0, lastColonIndex) + ']:' + port + path;
}
}
6 changes: 6 additions & 0 deletions schemas/server-common.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@
"type": "string",
"pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
},
"ipv6": {
"$anchor": "zoweIpv6",
"type": "string",
"description": "This pattern is oversimplified and can recognize ipv6 strings, but can also generate false positives in which a string similar to but not ipv6 will match. Do not use this as the only indicator of whether or not a string is ipv6",
"pattern": "^[0-9a-f:]*:[0-9a-f]{0,4}:[0-9a-f]{1,4}$"
},
"tcpPort": {
"$anchor": "zoweTcpPort",
"type": "integer",
Expand Down
7 changes: 5 additions & 2 deletions schemas/zowe-yaml-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@
},
"externalDomains": {
"type": "array",
"description": "List of domain names of how you access Zowe from your local computer.",
"description": "List of domain names, IPv4 or IPv6 addresses of how clients, such as browsers or the Zowe CLI, should use to access Zowe.",
"minItems": 1,
"uniqueItems": true,
"items": {
Expand Down Expand Up @@ -1163,7 +1163,10 @@
"type": "array",
"description": "The IP addresses which all of the Zowe servers will be binding on and listening to. Some servers may only support listening on the first element.",
"items": {
"$ref": "/schemas/v2/server-common#zoweIpv4"
"anyOf": [
{ "$ref": "/schemas/v2/server-common#zoweIpv4" },
{ "$ref": "/schemas/v2/server-common#zoweIpv6" }
]
}
},
"vipaIp": {
Expand Down
Loading