timonel 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +43 -809
- package/SECURITY.md +48 -20
- package/dist/lib/Rutter.d.ts +280 -0
- package/dist/lib/Rutter.d.ts.map +1 -1
- package/dist/lib/Rutter.js +353 -0
- package/dist/lib/Rutter.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/Rutter.js
CHANGED
|
@@ -1055,6 +1055,257 @@ export class Rutter {
|
|
|
1055
1055
|
this.capture(spc, `${spec.name}-secretproviderclass`);
|
|
1056
1056
|
return spc;
|
|
1057
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Add Azure Workload Identity ServiceAccount.
|
|
1060
|
+
* Creates a ServiceAccount with Azure Workload Identity annotations for secure authentication.
|
|
1061
|
+
*
|
|
1062
|
+
* @param spec - Azure Workload Identity ServiceAccount specification
|
|
1063
|
+
* @returns The created ServiceAccount ApiObject
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```typescript
|
|
1067
|
+
* rutter.addAzureWorkloadIdentityServiceAccount({
|
|
1068
|
+
* name: 'workload-identity-sa',
|
|
1069
|
+
* clientId: '12345678-1234-1234-1234-123456789012',
|
|
1070
|
+
* tenantId: '87654321-4321-4321-4321-210987654321',
|
|
1071
|
+
* tokenExpiration: 3600
|
|
1072
|
+
* });
|
|
1073
|
+
* ```
|
|
1074
|
+
*/
|
|
1075
|
+
addAzureWorkloadIdentityServiceAccount(spec) {
|
|
1076
|
+
// Validate all inputs first for security
|
|
1077
|
+
if (spec.tokenExpiration !== undefined &&
|
|
1078
|
+
(spec.tokenExpiration < 3600 || spec.tokenExpiration > 86400)) {
|
|
1079
|
+
throw new Error(`Azure Workload Identity ${spec.name}: tokenExpiration must be between 3600 and 86400 seconds`);
|
|
1080
|
+
}
|
|
1081
|
+
if (!spec.clientId?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
|
|
1082
|
+
throw new Error(`Azure Workload Identity ${spec.name}: Invalid client ID format`);
|
|
1083
|
+
}
|
|
1084
|
+
if (!spec.tenantId?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
|
|
1085
|
+
throw new Error(`Azure Workload Identity ${spec.name}: Invalid tenant ID format`);
|
|
1086
|
+
}
|
|
1087
|
+
const annotations = {
|
|
1088
|
+
'azure.workload.identity/client-id': spec.clientId,
|
|
1089
|
+
'azure.workload.identity/tenant-id': spec.tenantId,
|
|
1090
|
+
...(spec.tokenExpiration !== undefined
|
|
1091
|
+
? {
|
|
1092
|
+
'azure.workload.identity/service-account-token-expiration': String(spec.tokenExpiration),
|
|
1093
|
+
}
|
|
1094
|
+
: {}),
|
|
1095
|
+
...(spec.annotations ?? {}),
|
|
1096
|
+
};
|
|
1097
|
+
const sa = new ApiObject(this.chart, spec.name, {
|
|
1098
|
+
apiVersion: 'v1',
|
|
1099
|
+
kind: 'ServiceAccount',
|
|
1100
|
+
metadata: {
|
|
1101
|
+
name: spec.name,
|
|
1102
|
+
annotations,
|
|
1103
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1104
|
+
},
|
|
1105
|
+
automountServiceAccountToken: spec.automountServiceAccountToken,
|
|
1106
|
+
imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
|
|
1107
|
+
secrets: spec.secrets?.map((n) => ({ name: n })),
|
|
1108
|
+
});
|
|
1109
|
+
this.capture(sa, `${spec.name}-serviceaccount`);
|
|
1110
|
+
return sa;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Add Azure Key Vault SecretProviderClass.
|
|
1114
|
+
* Creates a SecretProviderClass for mounting secrets from Azure Key Vault using CSI driver.
|
|
1115
|
+
*
|
|
1116
|
+
* @param spec - Azure Key Vault SecretProviderClass specification
|
|
1117
|
+
* @returns The created SecretProviderClass ApiObject
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* ```typescript
|
|
1121
|
+
* rutter.addAzureKeyVaultSecretProviderClass({
|
|
1122
|
+
* name: 'app-secrets',
|
|
1123
|
+
* keyVaultName: 'my-keyvault',
|
|
1124
|
+
* tenantId: '87654321-4321-4321-4321-210987654321',
|
|
1125
|
+
* objects: [
|
|
1126
|
+
* { objectName: 'database-password', objectType: 'secret' },
|
|
1127
|
+
* { objectName: 'api-key', objectType: 'secret', objectAlias: 'API_KEY' }
|
|
1128
|
+
* ],
|
|
1129
|
+
* userAssignedIdentityID: '12345678-1234-1234-1234-123456789012'
|
|
1130
|
+
* });
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
addAzureKeyVaultSecretProviderClass(spec) {
|
|
1134
|
+
// Validate object names for security - prevent path traversal attacks
|
|
1135
|
+
spec.objects.forEach((obj) => {
|
|
1136
|
+
if (obj.objectName.includes('../') || obj.objectName.includes('..\\')) {
|
|
1137
|
+
throw new Error(`Azure Key Vault ${spec.name}: Object name '${obj.objectName}' contains invalid path traversal sequences`);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
const objects = spec.objects.map((obj) => {
|
|
1141
|
+
const baseObj = {
|
|
1142
|
+
objectName: obj.objectName,
|
|
1143
|
+
objectType: obj.objectType,
|
|
1144
|
+
};
|
|
1145
|
+
if (obj.objectAlias) {
|
|
1146
|
+
baseObj['objectAlias'] = obj.objectAlias;
|
|
1147
|
+
}
|
|
1148
|
+
if (obj.objectVersion) {
|
|
1149
|
+
baseObj['objectVersion'] = obj.objectVersion;
|
|
1150
|
+
}
|
|
1151
|
+
return baseObj;
|
|
1152
|
+
});
|
|
1153
|
+
const objectsYaml = YAML.stringify(objects, { indent: 2 }).trim();
|
|
1154
|
+
const parameters = {
|
|
1155
|
+
keyvaultName: spec.keyVaultName,
|
|
1156
|
+
tenantId: spec.tenantId,
|
|
1157
|
+
objects: `|\n ${objectsYaml.split('\n').join('\n ')}`,
|
|
1158
|
+
};
|
|
1159
|
+
if (spec.userAssignedIdentityID) {
|
|
1160
|
+
parameters['userAssignedIdentityID'] = spec.userAssignedIdentityID;
|
|
1161
|
+
}
|
|
1162
|
+
if (spec.cloudName) {
|
|
1163
|
+
parameters['cloudName'] = spec.cloudName;
|
|
1164
|
+
}
|
|
1165
|
+
const spc = new ApiObject(this.chart, spec.name, {
|
|
1166
|
+
apiVersion: 'secrets-store.csi.x-k8s.io/v1',
|
|
1167
|
+
kind: 'SecretProviderClass',
|
|
1168
|
+
metadata: {
|
|
1169
|
+
name: spec.name,
|
|
1170
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1171
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
1172
|
+
},
|
|
1173
|
+
spec: {
|
|
1174
|
+
provider: 'azure',
|
|
1175
|
+
parameters,
|
|
1176
|
+
},
|
|
1177
|
+
});
|
|
1178
|
+
this.capture(spc, `${spec.name}-secretproviderclass`);
|
|
1179
|
+
return spc;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Add Azure Files StorageClass.
|
|
1183
|
+
* Creates a StorageClass for dynamic provisioning of Azure Files volumes.
|
|
1184
|
+
*
|
|
1185
|
+
* @param spec - Azure Files StorageClass specification
|
|
1186
|
+
* @returns The created StorageClass ApiObject
|
|
1187
|
+
*
|
|
1188
|
+
* @example
|
|
1189
|
+
* ```typescript
|
|
1190
|
+
* rutter.addAzureFilesStorageClass({
|
|
1191
|
+
* name: 'azure-files-premium',
|
|
1192
|
+
* skuName: 'Premium_LRS',
|
|
1193
|
+
* protocol: 'smb',
|
|
1194
|
+
* allowSharedAccess: true
|
|
1195
|
+
* });
|
|
1196
|
+
* ```
|
|
1197
|
+
*/
|
|
1198
|
+
addAzureFilesStorageClass(spec) {
|
|
1199
|
+
// Validate Azure Files parameters for security and compliance
|
|
1200
|
+
this.validateAzureFilesParameters(spec);
|
|
1201
|
+
const parameters = this.buildAzureFilesParameters(spec);
|
|
1202
|
+
const sc = new ApiObject(this.chart, spec.name, {
|
|
1203
|
+
apiVersion: Rutter.STORAGE_API_VERSION,
|
|
1204
|
+
kind: 'StorageClass',
|
|
1205
|
+
metadata: {
|
|
1206
|
+
name: spec.name,
|
|
1207
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1208
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
1209
|
+
},
|
|
1210
|
+
provisioner: Rutter.AZURE_FILES_CSI_DRIVER,
|
|
1211
|
+
parameters,
|
|
1212
|
+
reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
|
|
1213
|
+
allowVolumeExpansion: spec.allowVolumeExpansion ?? true,
|
|
1214
|
+
volumeBindingMode: spec.volumeBindingMode ?? 'Immediate',
|
|
1215
|
+
...(spec.mountOptions ? { mountOptions: spec.mountOptions } : {}),
|
|
1216
|
+
});
|
|
1217
|
+
this.capture(sc, `${spec.name}-storageclass`);
|
|
1218
|
+
return sc;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Add Azure Files PersistentVolume.
|
|
1222
|
+
* Creates a PersistentVolume for static Azure Files volume provisioning.
|
|
1223
|
+
*
|
|
1224
|
+
* @param spec - Azure Files PersistentVolume specification
|
|
1225
|
+
* @returns The created PersistentVolume ApiObject
|
|
1226
|
+
*/
|
|
1227
|
+
addAzureFilesPersistentVolume(spec) {
|
|
1228
|
+
const csiSpec = this.buildAzureFilesPVCSISpec(spec);
|
|
1229
|
+
const pv = new ApiObject(this.chart, spec.name, {
|
|
1230
|
+
apiVersion: 'v1',
|
|
1231
|
+
kind: 'PersistentVolume',
|
|
1232
|
+
metadata: {
|
|
1233
|
+
name: spec.name,
|
|
1234
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1235
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
1236
|
+
},
|
|
1237
|
+
spec: {
|
|
1238
|
+
capacity: { storage: spec.capacity },
|
|
1239
|
+
accessModes: spec.accessModes ?? ['ReadWriteMany'],
|
|
1240
|
+
persistentVolumeReclaimPolicy: spec.reclaimPolicy ?? 'Retain',
|
|
1241
|
+
storageClassName: spec.storageClassName,
|
|
1242
|
+
...(spec.mountOptions ? { mountOptions: spec.mountOptions } : {}),
|
|
1243
|
+
csi: csiSpec,
|
|
1244
|
+
},
|
|
1245
|
+
});
|
|
1246
|
+
this.capture(pv, `${spec.name}-pv`);
|
|
1247
|
+
return pv;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Add Azure Files PersistentVolumeClaim.
|
|
1251
|
+
* Creates a PersistentVolumeClaim for Azure Files storage.
|
|
1252
|
+
*
|
|
1253
|
+
* @param spec - Azure Files PersistentVolumeClaim specification
|
|
1254
|
+
* @returns The created PersistentVolumeClaim ApiObject
|
|
1255
|
+
*/
|
|
1256
|
+
addAzureFilesPersistentVolumeClaim(spec) {
|
|
1257
|
+
const pvc = new ApiObject(this.chart, spec.name, {
|
|
1258
|
+
apiVersion: 'v1',
|
|
1259
|
+
kind: 'PersistentVolumeClaim',
|
|
1260
|
+
metadata: {
|
|
1261
|
+
name: spec.name,
|
|
1262
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1263
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
1264
|
+
},
|
|
1265
|
+
spec: {
|
|
1266
|
+
accessModes: spec.accessModes ?? ['ReadWriteMany'],
|
|
1267
|
+
storageClassName: spec.storageClassName,
|
|
1268
|
+
resources: {
|
|
1269
|
+
requests: { storage: spec.size },
|
|
1270
|
+
},
|
|
1271
|
+
},
|
|
1272
|
+
});
|
|
1273
|
+
this.capture(pvc, `${spec.name}-pvc`);
|
|
1274
|
+
return pvc;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Add Azure Container Registry ServiceAccount.
|
|
1278
|
+
* Creates a ServiceAccount configured for ACR access.
|
|
1279
|
+
*
|
|
1280
|
+
* @param spec - Azure ACR ServiceAccount specification
|
|
1281
|
+
* @returns The created ServiceAccount ApiObject
|
|
1282
|
+
*/
|
|
1283
|
+
addAzureACRServiceAccount(spec) {
|
|
1284
|
+
const annotations = {
|
|
1285
|
+
...(spec.annotations ?? {}),
|
|
1286
|
+
};
|
|
1287
|
+
// Add Workload Identity annotations if provided
|
|
1288
|
+
if (spec.clientId) {
|
|
1289
|
+
annotations['azure.workload.identity/client-id'] = spec.clientId;
|
|
1290
|
+
}
|
|
1291
|
+
if (spec.tenantId) {
|
|
1292
|
+
annotations['azure.workload.identity/tenant-id'] = spec.tenantId;
|
|
1293
|
+
}
|
|
1294
|
+
const sa = new ApiObject(this.chart, spec.name, {
|
|
1295
|
+
apiVersion: 'v1',
|
|
1296
|
+
kind: 'ServiceAccount',
|
|
1297
|
+
metadata: {
|
|
1298
|
+
name: spec.name,
|
|
1299
|
+
...(Object.keys(annotations).length ? { annotations } : {}),
|
|
1300
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
1301
|
+
},
|
|
1302
|
+
automountServiceAccountToken: spec.automountServiceAccountToken,
|
|
1303
|
+
imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
|
|
1304
|
+
secrets: spec.secrets?.map((n) => ({ name: n })),
|
|
1305
|
+
});
|
|
1306
|
+
this.capture(sa, `${spec.name}-serviceaccount`);
|
|
1307
|
+
return sa;
|
|
1308
|
+
}
|
|
1058
1309
|
addAzureDiskStorageClass(spec) {
|
|
1059
1310
|
// Validate numeric parameters to prevent runtime errors
|
|
1060
1311
|
this.validateAzureDiskNumericParameters(spec);
|
|
@@ -1435,6 +1686,76 @@ export class Rutter {
|
|
|
1435
1686
|
throw new Error('Port 443 requires an SSL certificate to be specified');
|
|
1436
1687
|
}
|
|
1437
1688
|
}
|
|
1689
|
+
buildAzureFilesParameters(spec) {
|
|
1690
|
+
const parameters = {
|
|
1691
|
+
skuName: spec.skuName ?? 'Standard_LRS',
|
|
1692
|
+
};
|
|
1693
|
+
if (spec.protocol) {
|
|
1694
|
+
parameters['protocol'] = spec.protocol;
|
|
1695
|
+
}
|
|
1696
|
+
if (spec.allowSharedAccess !== undefined) {
|
|
1697
|
+
parameters['allowSharedAccess'] = String(spec.allowSharedAccess);
|
|
1698
|
+
}
|
|
1699
|
+
if (spec.resourceGroup) {
|
|
1700
|
+
parameters['resourceGroup'] = spec.resourceGroup;
|
|
1701
|
+
}
|
|
1702
|
+
if (spec.storageAccount) {
|
|
1703
|
+
parameters['storageAccount'] = spec.storageAccount;
|
|
1704
|
+
}
|
|
1705
|
+
if (spec.location) {
|
|
1706
|
+
parameters['location'] = spec.location;
|
|
1707
|
+
}
|
|
1708
|
+
if (spec.networkEndpointType) {
|
|
1709
|
+
parameters['networkEndpointType'] = spec.networkEndpointType;
|
|
1710
|
+
}
|
|
1711
|
+
// NFS-specific parameters
|
|
1712
|
+
if (spec.protocol === 'nfs' && spec.mountPermissions) {
|
|
1713
|
+
parameters['mountPermissions'] = spec.mountPermissions;
|
|
1714
|
+
}
|
|
1715
|
+
if (spec.protocol === 'nfs' && spec.rootSquashType) {
|
|
1716
|
+
parameters['rootSquashType'] = spec.rootSquashType;
|
|
1717
|
+
}
|
|
1718
|
+
return parameters;
|
|
1719
|
+
}
|
|
1720
|
+
buildAzureFilesPVCSISpec(spec) {
|
|
1721
|
+
// Validate protocol and secret configuration
|
|
1722
|
+
if (spec.protocol === 'nfs' && spec.secretName) {
|
|
1723
|
+
throw new Error(`Azure Files PV ${spec.name}: NFS protocol does not use secrets for authentication`);
|
|
1724
|
+
}
|
|
1725
|
+
const volumeHandle = this.sanitizeVolumeHandle(spec.storageAccount, spec.shareName);
|
|
1726
|
+
const volumeAttributes = {
|
|
1727
|
+
storageAccount: spec.storageAccount,
|
|
1728
|
+
shareName: spec.shareName,
|
|
1729
|
+
};
|
|
1730
|
+
if (spec.resourceGroup) {
|
|
1731
|
+
volumeAttributes['resourceGroup'] = spec.resourceGroup;
|
|
1732
|
+
}
|
|
1733
|
+
if (spec.protocol) {
|
|
1734
|
+
volumeAttributes['protocol'] = spec.protocol;
|
|
1735
|
+
}
|
|
1736
|
+
if (spec.server) {
|
|
1737
|
+
volumeAttributes['server'] = spec.server;
|
|
1738
|
+
}
|
|
1739
|
+
if (spec.folderName) {
|
|
1740
|
+
volumeAttributes['folderName'] = spec.folderName;
|
|
1741
|
+
}
|
|
1742
|
+
if (spec.mountPermissions) {
|
|
1743
|
+
volumeAttributes['mountPermissions'] = spec.mountPermissions;
|
|
1744
|
+
}
|
|
1745
|
+
const csiSpec = {
|
|
1746
|
+
driver: Rutter.AZURE_FILES_CSI_DRIVER,
|
|
1747
|
+
volumeHandle,
|
|
1748
|
+
volumeAttributes,
|
|
1749
|
+
};
|
|
1750
|
+
// Add secret reference for SMB protocol
|
|
1751
|
+
if (spec.protocol !== 'nfs' && spec.secretName) {
|
|
1752
|
+
csiSpec['nodeStageSecretRef'] = {
|
|
1753
|
+
name: spec.secretName,
|
|
1754
|
+
namespace: spec.secretNamespace ?? 'default',
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
return csiSpec;
|
|
1758
|
+
}
|
|
1438
1759
|
buildPodEnvironment(spec) {
|
|
1439
1760
|
const envEntries = spec.env
|
|
1440
1761
|
? Object.entries(spec.env).map(([name, value]) => this.sanitizeEnvVar(name, value))
|
|
@@ -1641,6 +1962,37 @@ export class Rutter {
|
|
|
1641
1962
|
},
|
|
1642
1963
|
};
|
|
1643
1964
|
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Validate Azure Files parameters for security and compliance
|
|
1967
|
+
*/
|
|
1968
|
+
validateAzureFilesParameters(spec) {
|
|
1969
|
+
const validSkuNames = [
|
|
1970
|
+
'Standard_LRS',
|
|
1971
|
+
'Standard_GRS',
|
|
1972
|
+
'Standard_RAGRS',
|
|
1973
|
+
'Standard_ZRS',
|
|
1974
|
+
'Premium_LRS',
|
|
1975
|
+
'Premium_ZRS',
|
|
1976
|
+
];
|
|
1977
|
+
if (spec.skuName && !validSkuNames.includes(spec.skuName)) {
|
|
1978
|
+
throw new Error(`Invalid skuName: ${spec.skuName}. Must be one of: ${validSkuNames.join(', ')}`);
|
|
1979
|
+
}
|
|
1980
|
+
if (spec.protocol === 'nfs' && spec.mountPermissions) {
|
|
1981
|
+
const permRegex = /^[0-7]{3,4}$/;
|
|
1982
|
+
if (!permRegex.test(spec.mountPermissions)) {
|
|
1983
|
+
throw new Error('mountPermissions must be a valid octal permission string (e.g., "0777")');
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Sanitize volume handle to ensure valid format for CSI driver
|
|
1989
|
+
*/
|
|
1990
|
+
sanitizeVolumeHandle(account, share) {
|
|
1991
|
+
// Remove special characters and ensure valid format
|
|
1992
|
+
const sanitizedAccount = account.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
1993
|
+
const sanitizedShare = share.replace(/[^a-z0-9-]/gi, '').toLowerCase();
|
|
1994
|
+
return `${sanitizedAccount}#${sanitizedShare}`;
|
|
1995
|
+
}
|
|
1644
1996
|
/**
|
|
1645
1997
|
* Capture the YAML of an ApiObject or Construct into assets.
|
|
1646
1998
|
*/
|
|
@@ -1781,5 +2133,6 @@ Rutter.NETWORKING_API_VERSION = 'networking.k8s.io/v1';
|
|
|
1781
2133
|
Rutter.STORAGE_API_VERSION = 'storage.k8s.io/v1';
|
|
1782
2134
|
Rutter.AZURE_DISK_CSI_DRIVER = 'disk.csi.azure.com';
|
|
1783
2135
|
Rutter.AZURE_DISK_PROVISIONER_ANNOTATION = 'volume.beta.kubernetes.io/storage-provisioner';
|
|
2136
|
+
Rutter.AZURE_FILES_CSI_DRIVER = 'file.csi.azure.com';
|
|
1784
2137
|
Rutter.EMPTY_POD_SELECTOR = {};
|
|
1785
2138
|
//# sourceMappingURL=Rutter.js.map
|