@mizchi/k1c 0.3.0 → 0.4.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/README.md CHANGED
@@ -49,8 +49,9 @@ $ K1C_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... pnpm k1c apply -f manifest.yaml
49
49
  | `DNSRecord` (CRD) | DNS records | working |
50
50
  | `LogpushJob` (CRD) | Logpush (zone- or account-scoped) | working |
51
51
  | `ai` / `browser` / `version_metadata` / `analytics_engine` Worker bindings | annotation- / volume-driven | working |
52
- | `Ingress` (`networking.k8s.io/v1`) | generated router Worker + Custom Domain per host | working |
53
- | `CustomHostname` / Zero Trust Access | | not implemented (see [`TODO.md`](TODO.md)) |
52
+ | `Ingress` (`networking.k8s.io/v1`) | generated router Worker + Custom Domain per literal host (Workers Route per wildcard host) | working |
53
+ | `AccessApplication` (CRD) | Cloudflare Access self-hosted app with inline policies | working |
54
+ | `CustomHostname` | — | not implemented (see [`TODO.md`](TODO.md)) |
54
55
 
55
56
  See [`docs/resources.md`](docs/resources.md) for the full mapping and limitations,
56
57
  and [`TODO.md`](TODO.md) for what's queued.
@@ -31,6 +31,7 @@ export async function lower(resources, options) {
31
31
  const dnsRecords = [];
32
32
  const logpushJobs = [];
33
33
  const ingresses = [];
34
+ const accessApplications = [];
34
35
  for (const r of resources) {
35
36
  const label = labelOf(r);
36
37
  switch (r.kind) {
@@ -90,6 +91,9 @@ export async function lower(resources, options) {
90
91
  case 'Ingress':
91
92
  ingresses.push(r);
92
93
  break;
94
+ case 'AccessApplication':
95
+ accessApplications.push(r);
96
+ break;
93
97
  }
94
98
  }
95
99
  const desired = [];
@@ -154,8 +158,58 @@ export async function lower(resources, options) {
154
158
  desired.push(out);
155
159
  }
156
160
  }
161
+ for (const app of accessApplications) {
162
+ desired.push(lowerAccessApplication(app));
163
+ }
157
164
  return { desired, warnings };
158
165
  }
166
+ function ruleToWire(rule) {
167
+ if ('email' in rule)
168
+ return { email: { email: rule.email.email } };
169
+ if ('emailDomain' in rule)
170
+ return { email_domain: { domain: rule.emailDomain.domain } };
171
+ if ('everyone' in rule)
172
+ return { everyone: {} };
173
+ if ('ip' in rule)
174
+ return { ip: { ip: rule.ip.ip } };
175
+ if ('country' in rule)
176
+ return { country: { country_code: rule.country.code } };
177
+ if ('serviceToken' in rule)
178
+ return { service_token: { token_id: rule.serviceToken.tokenId } };
179
+ return { any_valid_service_token: {} };
180
+ }
181
+ function policyToWire(p) {
182
+ return {
183
+ name: p.name,
184
+ decision: p.decision,
185
+ include: p.include.map(ruleToWire),
186
+ ...(p.exclude !== undefined ? { exclude: p.exclude.map(ruleToWire) } : {}),
187
+ ...(p.require !== undefined ? { require: p.require.map(ruleToWire) } : {}),
188
+ ...(p.sessionDuration !== undefined ? { session_duration: p.sessionDuration } : {}),
189
+ };
190
+ }
191
+ function lowerAccessApplication(app) {
192
+ const ns = app.metadata.namespace ?? 'default';
193
+ const name = app.metadata.name;
194
+ const properties = {
195
+ appName: `k1c-${ns}-${name}`,
196
+ domain: app.spec.domain,
197
+ ...(app.spec.sessionDuration !== undefined
198
+ ? { sessionDuration: app.spec.sessionDuration }
199
+ : {}),
200
+ ...(app.spec.autoRedirectToIdentity !== undefined
201
+ ? { autoRedirectToIdentity: app.spec.autoRedirectToIdentity }
202
+ : {}),
203
+ ...(app.spec.allowedIdps !== undefined ? { allowedIdps: [...app.spec.allowedIdps] } : {}),
204
+ policies: app.spec.policies.map(policyToWire),
205
+ };
206
+ return {
207
+ resourceType: 'AccessApplication',
208
+ ref: refOf(app),
209
+ label: `${ns}/${name}`,
210
+ properties,
211
+ };
212
+ }
159
213
  function lowerVectorize(v) {
160
214
  const ns = v.metadata.namespace ?? 'default';
161
215
  const name = v.metadata.name;
@@ -336,25 +390,19 @@ async function lowerIngress(ing, tables, warnings, options) {
336
390
  throw new LowerError(`Ingress ${ns}/${name}: missing required annotation \`cloudflare.com/zone-id\``);
337
391
  }
338
392
  const literalHosts = new Set();
339
- let hasWildcardHost = false;
393
+ const wildcardHosts = new Set();
340
394
  for (const rule of ing.spec.rules) {
341
395
  if (rule.host === undefined)
342
396
  continue;
343
397
  if (rule.host.startsWith('*.')) {
344
- hasWildcardHost = true;
398
+ wildcardHosts.add(rule.host);
345
399
  }
346
400
  else {
347
401
  literalHosts.add(rule.host);
348
402
  }
349
403
  }
350
- if (literalHosts.size === 0) {
351
- throw new LowerError(`Ingress ${ns}/${name}: at least one rule must specify a literal (non-wildcard) host so a Custom Domain can be created`);
352
- }
353
- if (hasWildcardHost) {
354
- warnings.push({
355
- ref,
356
- message: `Ingress ${ns}/${name}: wildcard hosts are matched in-router only; create a Workers Route or extra Custom Domain manifest to actually receive traffic for the wildcard`,
357
- });
404
+ if (literalHosts.size === 0 && wildcardHosts.size === 0) {
405
+ throw new LowerError(`Ingress ${ns}/${name}: at least one rule must specify a host (literal or wildcard) so traffic can be bound to the router`);
358
406
  }
359
407
  // Assign one binding name per unique backend Service.
360
408
  const backendBindings = new Map(); // serviceName -> bindingName
@@ -454,6 +502,26 @@ async function lowerIngress(ing, tables, warnings, options) {
454
502
  dependsOn: [routerRef],
455
503
  });
456
504
  }
505
+ // Wildcard hosts cannot be bound via Custom Domain (which takes literal
506
+ // hostnames only); fall back to a zone-scoped Workers Route at `<host>/*`.
507
+ // The router Worker already matches the wildcard in-source, so this just
508
+ // delivers traffic to it.
509
+ for (const host of wildcardHosts) {
510
+ const pattern = `${host}/*`;
511
+ const routeRef = { ...ref, name: `${name}--route--${host}` };
512
+ const routeProps = {
513
+ zoneId,
514
+ pattern,
515
+ scriptName: routerScriptName,
516
+ };
517
+ results.push({
518
+ resourceType: 'WorkerRoute',
519
+ ref: routeRef,
520
+ label: pattern,
521
+ properties: routeProps,
522
+ dependsOn: [routerRef],
523
+ });
524
+ }
457
525
  return results;
458
526
  }
459
527
  function findWorkloadBySelector(selector, namespace, deployments, rollouts) {