@mesh-tech/mesh-cli 0.35.1 → 0.35.2

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/bin/mesh.js CHANGED
@@ -5710,14 +5710,69 @@ function toEnv(credentials) {
5710
5710
  AWS_SESSION_TOKEN: SessionToken
5711
5711
  };
5712
5712
  }
5713
- function isMaxSessionDurationError(err) {
5714
- if (!(err instanceof Error)) return false;
5715
- const message = err.message ?? "";
5716
- return message.includes("MaxSessionDuration") || message.includes("DurationSeconds");
5713
+ function isRoleChainedCaller(callerArn) {
5714
+ return !!callerArn && /^arn:aws[a-z-]*:sts::\d+:assumed-role\//.test(callerArn);
5717
5715
  }
5718
- async function assumeRole(roleArn, sessionName = "mesh-cli", durationSeconds = 43200) {
5719
- const sts = newStsClient();
5720
- for (const duration of [durationSeconds, 3600]) {
5716
+ function stsErrorDetail(err) {
5717
+ if (!err || typeof err !== "object") return { text: String(err) };
5718
+ const e = err;
5719
+ const str = (v) => typeof v === "string" && v ? v : void 0;
5720
+ const name = str(e.name);
5721
+ const typeCode = str(str(e.__type)?.split("#").pop());
5722
+ const code = str(e.Code) ?? typeCode ?? str(e.Error?.Code) ?? (name && name !== "Error" ? name : void 0);
5723
+ const text = str(e.Error?.Message) ?? str(e.Message) ?? str(e.message) ?? code ?? String(err);
5724
+ return { code, text };
5725
+ }
5726
+ function isSessionDurationError(err) {
5727
+ if (!err || typeof err !== "object") return false;
5728
+ const { code, text } = stsErrorDetail(err);
5729
+ if (code !== void 0 && code !== "ValidationError") return false;
5730
+ return /DurationSeconds|MaxSessionDuration|role chaining/i.test(text);
5731
+ }
5732
+ function formatDuration(seconds) {
5733
+ return seconds % 3600 === 0 ? `${seconds / 3600}h` : `${seconds}s`;
5734
+ }
5735
+ function describeAssumeRoleFailure(opts) {
5736
+ const reason = opts.code && opts.code !== opts.text ? `${opts.code}: ${opts.text}` : opts.text;
5737
+ const lines = [
5738
+ `AssumeRole failed for ${opts.roleArn} \u2014 requested a ${formatDuration(opts.durationSeconds)} session (DurationSeconds=${opts.durationSeconds}): ${reason}`
5739
+ ];
5740
+ if (opts.callerArn) lines.push(` Caller: ${opts.callerArn}`);
5741
+ const zitadel = `mesh login ${opts.loginContext ?? "<platform>.<env>"}`;
5742
+ if (isExpiredAwsTokenMessage(`${opts.code ?? ""} ${opts.text}`)) {
5743
+ lines.push(` Your AWS SSO session looks expired or missing \u2014 refresh with: ${SSO_LOGIN_FIX}`);
5744
+ lines.push(` Or assume the role via Zitadel instead: ${zitadel}`);
5745
+ } else if (opts.code === "AccessDenied" || /not authorized/i.test(opts.text)) {
5746
+ lines.push(" The role does not trust this AWS identity. Assume it via Zitadel instead:");
5747
+ lines.push(` ${zitadel}`);
5748
+ lines.push(" or switch to an AWS profile the role's trust policy allows.");
5749
+ } else if (isSessionDurationError({ Code: opts.code, message: opts.text })) {
5750
+ lines.push(
5751
+ ` The role refused even a ${formatDuration(ROLE_CHAINING_MAX_SESSION_SECONDS)} session. Check its MaxSessionDuration, or assume it via Zitadel instead:`
5752
+ );
5753
+ lines.push(` ${zitadel}`);
5754
+ } else {
5755
+ lines.push(` Try assuming the role via Zitadel: ${zitadel}`);
5756
+ lines.push(` or refresh the AWS session: ${SSO_LOGIN_FIX}`);
5757
+ }
5758
+ return lines.join("\n");
5759
+ }
5760
+ async function assumeRole(roleArn, sessionName = "mesh-cli", durationSeconds = DEFAULT_ASSUME_ROLE_SECONDS, options = {}) {
5761
+ const sts = options.sts ?? newStsClient();
5762
+ let callerArn;
5763
+ try {
5764
+ callerArn = (await sts.send(new GetCallerIdentityCommand({}))).Arn;
5765
+ } catch {
5766
+ }
5767
+ let requested = durationSeconds;
5768
+ if (isRoleChainedCaller(callerArn) && requested > ROLE_CHAINING_MAX_SESSION_SECONDS) {
5769
+ requested = ROLE_CHAINING_MAX_SESSION_SECONDS;
5770
+ logInfo(
5771
+ `Current AWS identity is an assumed-role session \u2014 requesting a ${formatDuration(requested)} session (role chaining caps AssumeRole at one hour)`
5772
+ );
5773
+ }
5774
+ const attempts = requested > ROLE_CHAINING_MAX_SESSION_SECONDS ? [requested, ROLE_CHAINING_MAX_SESSION_SECONDS] : [requested];
5775
+ for (const [i, duration] of attempts.entries()) {
5721
5776
  try {
5722
5777
  const resp = await sts.send(
5723
5778
  new AssumeRoleCommand({
@@ -5729,14 +5784,25 @@ async function assumeRole(roleArn, sessionName = "mesh-cli", durationSeconds = 4
5729
5784
  if (!resp.Credentials) return null;
5730
5785
  return toEnv(resp.Credentials);
5731
5786
  } catch (err) {
5732
- if (!isMaxSessionDurationError(err) || duration === 3600) {
5733
- const message = err instanceof Error ? err.message : String(err);
5734
- logWarn(`AssumeRole failed: ${message}`);
5735
- if (isExpiredAwsTokenMessage(message)) {
5736
- logWarn(` Your AWS SSO session looks expired or missing \u2014 refresh with: ${SSO_LOGIN_FIX}`);
5737
- }
5738
- return null;
5787
+ const isLast = i === attempts.length - 1;
5788
+ if (!isLast && isSessionDurationError(err)) {
5789
+ logInfo(
5790
+ `STS refused a ${formatDuration(duration)} session for ${roleArn} \u2014 retrying with ${formatDuration(ROLE_CHAINING_MAX_SESSION_SECONDS)}`
5791
+ );
5792
+ continue;
5739
5793
  }
5794
+ const { code, text } = stsErrorDetail(err);
5795
+ logWarn(
5796
+ describeAssumeRoleFailure({
5797
+ roleArn,
5798
+ durationSeconds: duration,
5799
+ code,
5800
+ text,
5801
+ callerArn,
5802
+ loginContext: options.loginContext
5803
+ })
5804
+ );
5805
+ return null;
5740
5806
  }
5741
5807
  }
5742
5808
  return null;
@@ -5766,9 +5832,10 @@ async function assumeRoleCredentials(roleArn, idToken, sessionName, durationSeco
5766
5832
  Expiration: resp.Credentials.Expiration.toISOString()
5767
5833
  };
5768
5834
  } catch (err) {
5769
- if (!isMaxSessionDurationError(err) || duration === 3600) {
5835
+ if (!isSessionDurationError(err) || duration === ROLE_CHAINING_MAX_SESSION_SECONDS) {
5836
+ const { code, text } = stsErrorDetail(err);
5770
5837
  logWarn(
5771
- `AssumeRoleWithWebIdentity failed: ${err instanceof Error ? err.message : String(err)}`
5838
+ `AssumeRoleWithWebIdentity failed: ${code && code !== text ? `${code}: ${text}` : text}`
5772
5839
  );
5773
5840
  return null;
5774
5841
  }
@@ -5884,7 +5951,9 @@ async function resolveAwsCredentials(roleArn, appRoot, stack) {
5884
5951
  }
5885
5952
  }
5886
5953
  logInfo(`Assuming ${roleName} via AWS SSO`);
5887
- const creds = await assumeRole(roleArn, "mesh-cli");
5954
+ const creds = await assumeRole(roleArn, "mesh-cli", DEFAULT_ASSUME_ROLE_SECONDS, {
5955
+ loginContext: platformContext ?? void 0
5956
+ });
5888
5957
  if (creds) {
5889
5958
  logSuccess(`Assumed ${roleName}`);
5890
5959
  return { env: creds, method: "sso" };
@@ -5903,7 +5972,7 @@ async function resolveAwsCredentials(roleArn, appRoot, stack) {
5903
5972
  }
5904
5973
  return null;
5905
5974
  }
5906
- var DEFAULT_REGION, MANAGED_START_PREFIX, MANAGED_START, MANAGED_END;
5975
+ var DEFAULT_REGION, MANAGED_START_PREFIX, MANAGED_START, MANAGED_END, ROLE_CHAINING_MAX_SESSION_SECONDS, DEFAULT_ASSUME_ROLE_SECONDS;
5907
5976
  var init_aws_auth = __esm({
5908
5977
  "packages/mesh-cli/src/utils/aws-auth.ts"() {
5909
5978
  "use strict";
@@ -5916,6 +5985,8 @@ var init_aws_auth = __esm({
5916
5985
  MANAGED_START_PREFIX = `# >>> mesh-cli managed profile `;
5917
5986
  MANAGED_START = (context) => `${MANAGED_START_PREFIX}${context} >>>`;
5918
5987
  MANAGED_END = `# <<< mesh-cli managed <<<`;
5988
+ ROLE_CHAINING_MAX_SESSION_SECONDS = 3600;
5989
+ DEFAULT_ASSUME_ROLE_SECONDS = 43200;
5919
5990
  }
5920
5991
  });
5921
5992
 
@@ -7037,6 +7108,7 @@ var init_src = __esm({
7037
7108
  name: z.string().min(1),
7038
7109
  kind: apiSurfaceKindSchema.optional(),
7039
7110
  provider: z.string().optional(),
7111
+ externalService: z.string().min(1).optional(),
7040
7112
  version: z.string().optional(),
7041
7113
  title: z.string().optional(),
7042
7114
  description: z.string().optional(),