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.
@@ -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