@intentius/chant-lexicon-gcp 0.0.22 → 0.1.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/dist/integrity.json +7 -7
- package/dist/manifest.json +1 -1
- package/dist/rules/wgc101.ts +1 -1
- package/dist/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
- package/dist/skills/chant-gcp-patterns.md +3 -2
- package/dist/skills/chant-gcp-security.md +3 -2
- package/dist/skills/chant-gcp.md +360 -28
- package/package.json +5 -2
- package/src/composites/cloud-function.ts +23 -15
- package/src/composites/cloud-run-service.ts +20 -13
- package/src/composites/cloud-sql-instance.ts +31 -15
- package/src/composites/composites.test.ts +94 -62
- package/src/composites/gcs-bucket.ts +13 -9
- package/src/composites/gke-cluster.ts +91 -16
- package/src/composites/index.ts +13 -11
- package/src/composites/managed-certificate.ts +19 -15
- package/src/composites/memorystore-redis.ts +101 -0
- package/src/composites/private-service.ts +23 -15
- package/src/composites/pubsub-pipeline.ts +30 -18
- package/src/composites/secure-project.ts +42 -27
- package/src/composites/vpc-network.ts +52 -35
- package/src/index.ts +13 -11
- package/src/lint/post-synth/post-synth.test.ts +2 -1
- package/src/lint/post-synth/wgc101.test.ts +2 -1
- package/src/lint/post-synth/wgc101.ts +1 -1
- package/src/plugin.ts +92 -180
- package/src/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
- package/src/skills/chant-gcp-patterns.md +3 -2
- package/src/skills/chant-gcp-security.md +3 -2
- package/src/skills/chant-gcp.md +360 -28
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* SecureProject composite — Project + IAMAuditConfig + Service enablement + owner IAM + LoggingSink.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
6
|
+
import {
|
|
7
|
+
ResourcemanagerProject,
|
|
8
|
+
IAMAuditConfig,
|
|
9
|
+
ServiceusageService,
|
|
10
|
+
IAMPolicyMember,
|
|
11
|
+
LogSink,
|
|
12
|
+
} from "../generated";
|
|
13
|
+
|
|
5
14
|
export interface SecureProjectProps {
|
|
6
15
|
/** Project name (also used as project ID). */
|
|
7
16
|
name: string;
|
|
@@ -23,17 +32,16 @@ export interface SecureProjectProps {
|
|
|
23
32
|
labels?: Record<string, string>;
|
|
24
33
|
/** Namespace for all resources. */
|
|
25
34
|
namespace?: string;
|
|
35
|
+
/** Per-member defaults for customizing individual resources. */
|
|
36
|
+
defaults?: {
|
|
37
|
+
project?: Partial<ConstructorParameters<typeof ResourcemanagerProject>[0]>;
|
|
38
|
+
auditConfig?: Partial<ConstructorParameters<typeof IAMAuditConfig>[0]>;
|
|
39
|
+
ownerIam?: Partial<ConstructorParameters<typeof IAMPolicyMember>[0]>;
|
|
40
|
+
loggingSink?: Partial<ConstructorParameters<typeof LogSink>[0]>;
|
|
41
|
+
};
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
export
|
|
29
|
-
project: Record<string, unknown>;
|
|
30
|
-
auditConfig: Record<string, unknown>;
|
|
31
|
-
services: Record<string, unknown>[];
|
|
32
|
-
ownerIam?: Record<string, unknown>;
|
|
33
|
-
loggingSink?: Record<string, unknown>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
44
|
+
export const SecureProject = Composite<SecureProjectProps>((props) => {
|
|
37
45
|
const {
|
|
38
46
|
name,
|
|
39
47
|
orgId,
|
|
@@ -51,6 +59,7 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
51
59
|
loggingSinkFilter = 'logName:"cloudaudit.googleapis.com"',
|
|
52
60
|
labels: extraLabels = {},
|
|
53
61
|
namespace,
|
|
62
|
+
defaults: defs,
|
|
54
63
|
} = props;
|
|
55
64
|
|
|
56
65
|
const commonLabels: Record<string, string> = {
|
|
@@ -59,7 +68,7 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
59
68
|
...extraLabels,
|
|
60
69
|
};
|
|
61
70
|
|
|
62
|
-
const project
|
|
71
|
+
const project = new ResourcemanagerProject(mergeDefaults({
|
|
63
72
|
metadata: {
|
|
64
73
|
name,
|
|
65
74
|
...(namespace && { namespace }),
|
|
@@ -68,9 +77,9 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
68
77
|
...(orgId && { organizationRef: { external: orgId } }),
|
|
69
78
|
...(folderId && { folderRef: { external: folderId } }),
|
|
70
79
|
...(billingAccountRef && { billingAccountRef: { external: billingAccountRef } }),
|
|
71
|
-
};
|
|
80
|
+
} as Record<string, unknown>, defs?.project));
|
|
72
81
|
|
|
73
|
-
const auditConfig
|
|
82
|
+
const auditConfig = new IAMAuditConfig(mergeDefaults({
|
|
74
83
|
metadata: {
|
|
75
84
|
name: `${name}-audit`,
|
|
76
85
|
...(namespace && { namespace }),
|
|
@@ -82,21 +91,27 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
82
91
|
{ logType: "DATA_READ" },
|
|
83
92
|
{ logType: "DATA_WRITE" },
|
|
84
93
|
],
|
|
85
|
-
};
|
|
94
|
+
} as Record<string, unknown>, defs?.auditConfig));
|
|
86
95
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
// Spread services into named members (service_compute, service_container, etc.)
|
|
97
|
+
// so the Composite validator accepts them as individual Declarables.
|
|
98
|
+
const serviceEntries: Record<string, any> = {};
|
|
99
|
+
for (const api of enabledApis) {
|
|
100
|
+
const key = `service_${api.split(".")[0]}`;
|
|
101
|
+
serviceEntries[key] = new ServiceusageService({
|
|
102
|
+
metadata: {
|
|
103
|
+
name: `${name}-${api.split(".")[0]}`,
|
|
104
|
+
...(namespace && { namespace }),
|
|
105
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "service" },
|
|
106
|
+
},
|
|
107
|
+
resourceID: api,
|
|
108
|
+
} as Record<string, unknown>);
|
|
109
|
+
}
|
|
95
110
|
|
|
96
|
-
const result:
|
|
111
|
+
const result: Record<string, any> = { project, auditConfig, ...serviceEntries };
|
|
97
112
|
|
|
98
113
|
if (owner) {
|
|
99
|
-
result.ownerIam = {
|
|
114
|
+
result.ownerIam = new IAMPolicyMember(mergeDefaults({
|
|
100
115
|
metadata: {
|
|
101
116
|
name: `${name}-owner`,
|
|
102
117
|
...(namespace && { namespace }),
|
|
@@ -109,11 +124,11 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
109
124
|
kind: "Project",
|
|
110
125
|
name,
|
|
111
126
|
},
|
|
112
|
-
};
|
|
127
|
+
} as Record<string, unknown>, defs?.ownerIam));
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
if (loggingSinkDestination) {
|
|
116
|
-
result.loggingSink = {
|
|
131
|
+
result.loggingSink = new LogSink(mergeDefaults({
|
|
117
132
|
metadata: {
|
|
118
133
|
name: `${name}-audit-sink`,
|
|
119
134
|
...(namespace && { namespace }),
|
|
@@ -121,8 +136,8 @@ export function SecureProject(props: SecureProjectProps): SecureProjectResult {
|
|
|
121
136
|
},
|
|
122
137
|
destination: loggingSinkDestination,
|
|
123
138
|
filter: loggingSinkFilter,
|
|
124
|
-
};
|
|
139
|
+
} as Record<string, unknown>, defs?.loggingSink));
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
return result;
|
|
128
|
-
}
|
|
143
|
+
}, "SecureProject");
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
* VpcNetwork composite — ComputeNetwork + ComputeSubnetwork + ComputeFirewall + ComputeRouterNAT.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
6
|
+
import { VPCNetwork, Subnetwork, Firewall, Router, RouterNAT } from "../generated";
|
|
7
|
+
|
|
8
|
+
export interface VpcSubnetSecondaryRange {
|
|
9
|
+
rangeName: string;
|
|
10
|
+
ipCidrRange: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export interface VpcSubnet {
|
|
6
14
|
/** Subnet name suffix. */
|
|
7
15
|
name: string;
|
|
@@ -11,6 +19,8 @@ export interface VpcSubnet {
|
|
|
11
19
|
region: string;
|
|
12
20
|
/** Enable private Google access (default: true). */
|
|
13
21
|
privateIpGoogleAccess?: boolean;
|
|
22
|
+
/** Secondary IP ranges for VPC-native GKE pods/services. */
|
|
23
|
+
secondaryIpRanges?: VpcSubnetSecondaryRange[];
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
export interface VpcNetworkProps {
|
|
@@ -32,14 +42,12 @@ export interface VpcNetworkProps {
|
|
|
32
42
|
labels?: Record<string, string>;
|
|
33
43
|
/** Namespace for all resources. */
|
|
34
44
|
namespace?: string;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
router?: Record<string, unknown>;
|
|
42
|
-
routerNat?: Record<string, unknown>;
|
|
45
|
+
/** Per-member defaults for customizing individual resources. */
|
|
46
|
+
defaults?: {
|
|
47
|
+
network?: Partial<ConstructorParameters<typeof VPCNetwork>[0]>;
|
|
48
|
+
router?: Partial<ConstructorParameters<typeof Router>[0]>;
|
|
49
|
+
routerNat?: Partial<ConstructorParameters<typeof RouterNAT>[0]>;
|
|
50
|
+
};
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/**
|
|
@@ -60,7 +68,7 @@ export interface VpcNetworkResult {
|
|
|
60
68
|
* });
|
|
61
69
|
* ```
|
|
62
70
|
*/
|
|
63
|
-
export
|
|
71
|
+
export const VpcNetwork = Composite<VpcNetworkProps>((props) => {
|
|
64
72
|
const {
|
|
65
73
|
name,
|
|
66
74
|
autoCreateSubnetworks = false,
|
|
@@ -71,6 +79,7 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
|
|
|
71
79
|
allowIapSsh = false,
|
|
72
80
|
labels: extraLabels = {},
|
|
73
81
|
namespace,
|
|
82
|
+
defaults: defs,
|
|
74
83
|
} = props;
|
|
75
84
|
|
|
76
85
|
const commonLabels: Record<string, string> = {
|
|
@@ -79,7 +88,7 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
|
|
|
79
88
|
...extraLabels,
|
|
80
89
|
};
|
|
81
90
|
|
|
82
|
-
const network
|
|
91
|
+
const network = new VPCNetwork(mergeDefaults({
|
|
83
92
|
metadata: {
|
|
84
93
|
name,
|
|
85
94
|
...(namespace && { namespace }),
|
|
@@ -87,58 +96,66 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
|
|
|
87
96
|
},
|
|
88
97
|
autoCreateSubnetworks,
|
|
89
98
|
routingMode: "REGIONAL",
|
|
90
|
-
};
|
|
99
|
+
} as Record<string, unknown>, defs?.network));
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// Spread subnets into named members (subnet_<name>) for Composite validation.
|
|
102
|
+
const subnetEntries: Record<string, any> = {};
|
|
103
|
+
for (const sub of subnetDefs) {
|
|
104
|
+
subnetEntries[`subnet_${sub.name}`] = new Subnetwork({
|
|
105
|
+
metadata: {
|
|
106
|
+
name: `${name}-${sub.name}`,
|
|
107
|
+
...(namespace && { namespace }),
|
|
108
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "subnet" },
|
|
109
|
+
},
|
|
110
|
+
networkRef: { name },
|
|
111
|
+
ipCidrRange: sub.ipCidrRange,
|
|
112
|
+
region: sub.region,
|
|
113
|
+
privateIpGoogleAccess: sub.privateIpGoogleAccess ?? true,
|
|
114
|
+
...(sub.secondaryIpRanges && sub.secondaryIpRanges.length > 0 && {
|
|
115
|
+
secondaryIpRange: sub.secondaryIpRanges,
|
|
116
|
+
}),
|
|
117
|
+
} as Record<string, unknown>);
|
|
118
|
+
}
|
|
103
119
|
|
|
104
|
-
|
|
120
|
+
// Spread firewalls into named members for Composite validation.
|
|
121
|
+
const firewallEntries: Record<string, any> = {};
|
|
105
122
|
|
|
106
123
|
if (allowInternalTraffic) {
|
|
107
|
-
|
|
124
|
+
firewallEntries.firewallAllowInternal = new Firewall({
|
|
108
125
|
metadata: {
|
|
109
126
|
name: `${name}-allow-internal`,
|
|
110
127
|
...(namespace && { namespace }),
|
|
111
128
|
labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
|
|
112
129
|
},
|
|
113
130
|
networkRef: { name },
|
|
114
|
-
|
|
131
|
+
allow: [
|
|
115
132
|
{ protocol: "tcp", ports: ["0-65535"] },
|
|
116
133
|
{ protocol: "udp", ports: ["0-65535"] },
|
|
117
134
|
{ protocol: "icmp" },
|
|
118
135
|
],
|
|
119
136
|
sourceRanges: subnetDefs.map((s) => s.ipCidrRange),
|
|
120
|
-
});
|
|
137
|
+
} as Record<string, unknown>);
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
if (allowIapSsh) {
|
|
124
|
-
|
|
141
|
+
firewallEntries.firewallAllowIapSsh = new Firewall({
|
|
125
142
|
metadata: {
|
|
126
143
|
name: `${name}-allow-iap-ssh`,
|
|
127
144
|
...(namespace && { namespace }),
|
|
128
145
|
labels: { ...commonLabels, "app.kubernetes.io/component": "firewall" },
|
|
129
146
|
},
|
|
130
147
|
networkRef: { name },
|
|
131
|
-
|
|
148
|
+
allow: [{ protocol: "tcp", ports: ["22"] }],
|
|
132
149
|
sourceRanges: ["35.235.240.0/20"], // IAP IP range
|
|
133
|
-
});
|
|
150
|
+
} as Record<string, unknown>);
|
|
134
151
|
}
|
|
135
152
|
|
|
136
|
-
const result:
|
|
153
|
+
const result: Record<string, any> = { network, ...subnetEntries, ...firewallEntries };
|
|
137
154
|
|
|
138
155
|
if (enableNat && natRegion) {
|
|
139
156
|
const routerName = `${name}-router`;
|
|
140
157
|
|
|
141
|
-
result.router = {
|
|
158
|
+
result.router = new Router(mergeDefaults({
|
|
142
159
|
metadata: {
|
|
143
160
|
name: routerName,
|
|
144
161
|
...(namespace && { namespace }),
|
|
@@ -146,9 +163,9 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
|
|
|
146
163
|
},
|
|
147
164
|
networkRef: { name },
|
|
148
165
|
region: natRegion,
|
|
149
|
-
};
|
|
166
|
+
} as Record<string, unknown>, defs?.router));
|
|
150
167
|
|
|
151
|
-
result.routerNat = {
|
|
168
|
+
result.routerNat = new RouterNAT(mergeDefaults({
|
|
152
169
|
metadata: {
|
|
153
170
|
name: `${name}-nat`,
|
|
154
171
|
...(namespace && { namespace }),
|
|
@@ -158,8 +175,8 @@ export function VpcNetwork(props: VpcNetworkProps): VpcNetworkResult {
|
|
|
158
175
|
region: natRegion,
|
|
159
176
|
natIpAllocateOption: "AUTO_ONLY",
|
|
160
177
|
sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
|
|
161
|
-
};
|
|
178
|
+
} as Record<string, unknown>, defs?.routerNat));
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
return result;
|
|
165
|
-
}
|
|
182
|
+
}, "VpcNetwork");
|
package/src/index.ts
CHANGED
|
@@ -20,20 +20,22 @@ export * from "./generated/index";
|
|
|
20
20
|
|
|
21
21
|
// Composites
|
|
22
22
|
export {
|
|
23
|
-
GkeCluster,
|
|
23
|
+
GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork,
|
|
24
24
|
PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject,
|
|
25
|
+
MemorystoreRedis,
|
|
25
26
|
} from "./composites/index";
|
|
26
27
|
export type {
|
|
27
|
-
GkeClusterProps,
|
|
28
|
-
CloudRunServiceProps,
|
|
29
|
-
CloudSqlInstanceProps,
|
|
30
|
-
GcsBucketProps,
|
|
31
|
-
VpcNetworkProps,
|
|
32
|
-
PubSubPipelineProps,
|
|
33
|
-
CloudFunctionWithTriggerProps,
|
|
34
|
-
PrivateServiceProps,
|
|
35
|
-
ManagedCertificateProps,
|
|
36
|
-
SecureProjectProps,
|
|
28
|
+
GkeClusterProps,
|
|
29
|
+
CloudRunServiceProps,
|
|
30
|
+
CloudSqlInstanceProps,
|
|
31
|
+
GcsBucketProps,
|
|
32
|
+
VpcNetworkProps, VpcSubnet,
|
|
33
|
+
PubSubPipelineProps,
|
|
34
|
+
CloudFunctionWithTriggerProps,
|
|
35
|
+
PrivateServiceProps,
|
|
36
|
+
ManagedCertificateProps,
|
|
37
|
+
SecureProjectProps,
|
|
38
|
+
MemorystoreRedisProps,
|
|
37
39
|
} from "./composites/index";
|
|
38
40
|
|
|
39
41
|
// IAM role constants
|
|
@@ -54,7 +54,8 @@ metadata:
|
|
|
54
54
|
spec:
|
|
55
55
|
location: US
|
|
56
56
|
encryption:
|
|
57
|
-
|
|
57
|
+
kmsKeyRef:
|
|
58
|
+
external: projects/p/locations/l/keyRings/kr/cryptoKeys/k
|
|
58
59
|
`;
|
|
59
60
|
const diags = wgc101.check(makeCtx(yaml));
|
|
60
61
|
const bucketDiags = diags.filter(d => d.entity === "my-bucket");
|
|
@@ -30,7 +30,8 @@ metadata:
|
|
|
30
30
|
spec:
|
|
31
31
|
location: US
|
|
32
32
|
encryption:
|
|
33
|
-
|
|
33
|
+
kmsKeyRef:
|
|
34
|
+
external: projects/p/locations/l/keyRings/kr/cryptoKeys/k
|
|
34
35
|
`;
|
|
35
36
|
const diags = wgc101.check(makeCtx(yaml));
|
|
36
37
|
const bucketDiags = diags.filter(d => d.entity === "my-bucket");
|
|
@@ -30,7 +30,7 @@ export const wgc101: PostSynthCheck = {
|
|
|
30
30
|
diagnostics.push({
|
|
31
31
|
checkId: "WGC101",
|
|
32
32
|
severity: "warning",
|
|
33
|
-
message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.
|
|
33
|
+
message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.kmsKeyRef`,
|
|
34
34
|
entity: name,
|
|
35
35
|
lexicon: "gcp",
|
|
36
36
|
});
|