mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3775 from Infisical/ENG-2861
feat(machine-identity): Alibaba Cloud
This commit is contained in:
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@@ -65,6 +65,7 @@ import { TGroupProjectServiceFactory } from "@app/services/group-project/group-p
|
||||
import { THsmServiceFactory } from "@app/services/hsm/hsm-service";
|
||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||
import { TIdentityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
|
||||
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
||||
@@ -218,6 +219,7 @@ declare module "fastify" {
|
||||
identityUa: TIdentityUaServiceFactory;
|
||||
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
|
||||
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
||||
identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory;
|
||||
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||
identityOciAuth: TIdentityOciAuthServiceFactory;
|
||||
|
||||
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@@ -125,6 +125,9 @@ import {
|
||||
TIdentityAccessTokens,
|
||||
TIdentityAccessTokensInsert,
|
||||
TIdentityAccessTokensUpdate,
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAlicloudAuthsInsert,
|
||||
TIdentityAlicloudAuthsUpdate,
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAwsAuthsInsert,
|
||||
TIdentityAwsAuthsUpdate,
|
||||
@@ -786,6 +789,11 @@ declare module "knex/types/tables" {
|
||||
TIdentityGcpAuthsInsert,
|
||||
TIdentityGcpAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityAliCloudAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAlicloudAuthsInsert,
|
||||
TIdentityAlicloudAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAwsAuthsInsert,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.IdentityAliCloudAuth))) {
|
||||
await knex.schema.createTable(TableName.IdentityAliCloudAuth, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||
t.timestamps(true, true, true);
|
||||
t.uuid("identityId").notNullable().unique();
|
||||
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||
t.string("type").notNullable();
|
||||
|
||||
t.string("allowedArns").notNullable();
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.IdentityAliCloudAuth);
|
||||
await dropOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||
}
|
||||
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// Code generated by automation script, DO NOT EDIT.
|
||||
// Automated by pulling database and generating zod schema
|
||||
// To update. Just run npm run generate:schema
|
||||
// Written by akhilmhdh.
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const IdentityAlicloudAuthsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
accessTokenTTL: z.coerce.number().default(7200),
|
||||
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||
accessTokenTrustedIps: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
identityId: z.string().uuid(),
|
||||
type: z.string(),
|
||||
allowedArns: z.string()
|
||||
});
|
||||
|
||||
export type TIdentityAlicloudAuths = z.infer<typeof IdentityAlicloudAuthsSchema>;
|
||||
export type TIdentityAlicloudAuthsInsert = Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>;
|
||||
export type TIdentityAlicloudAuthsUpdate = Partial<Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>>;
|
||||
@@ -39,6 +39,7 @@ export * from "./group-project-memberships";
|
||||
export * from "./groups";
|
||||
export * from "./identities";
|
||||
export * from "./identity-access-tokens";
|
||||
export * from "./identity-alicloud-auths";
|
||||
export * from "./identity-aws-auths";
|
||||
export * from "./identity-azure-auths";
|
||||
export * from "./identity-gcp-auths";
|
||||
|
||||
@@ -80,6 +80,7 @@ export enum TableName {
|
||||
IdentityGcpAuth = "identity_gcp_auths",
|
||||
IdentityAzureAuth = "identity_azure_auths",
|
||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||
IdentityAliCloudAuth = "identity_alicloud_auths",
|
||||
IdentityAwsAuth = "identity_aws_auths",
|
||||
IdentityOciAuth = "identity_oci_auths",
|
||||
IdentityOidcAuth = "identity_oidc_auths",
|
||||
@@ -247,6 +248,7 @@ export enum IdentityAuthMethod {
|
||||
UNIVERSAL_AUTH = "universal-auth",
|
||||
KUBERNETES_AUTH = "kubernetes-auth",
|
||||
GCP_AUTH = "gcp-auth",
|
||||
ALICLOUD_AUTH = "alicloud-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
OCI_AUTH = "oci-auth",
|
||||
|
||||
@@ -170,6 +170,12 @@ export enum EventType {
|
||||
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||
|
||||
LOGIN_IDENTITY_ALICLOUD_AUTH = "login-identity-alicloud-auth",
|
||||
ADD_IDENTITY_ALICLOUD_AUTH = "add-identity-alicloud-auth",
|
||||
UPDATE_IDENTITY_ALICLOUD_AUTH = "update-identity-alicloud-auth",
|
||||
REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth",
|
||||
GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth",
|
||||
|
||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||
@@ -1060,6 +1066,53 @@ interface GetIdentityAwsAuthEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityAliCloudAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
identityAliCloudAuthId: string;
|
||||
identityAccessTokenId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddIdentityAliCloudAuthEvent {
|
||||
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteIdentityAliCloudAuthEvent {
|
||||
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateIdentityAliCloudAuthEvent {
|
||||
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetIdentityAliCloudAuthEvent {
|
||||
type: EventType.GET_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityOciAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
||||
metadata: {
|
||||
@@ -3272,6 +3325,11 @@ export type Event =
|
||||
| UpdateIdentityAwsAuthEvent
|
||||
| GetIdentityAwsAuthEvent
|
||||
| DeleteIdentityAwsAuthEvent
|
||||
| LoginIdentityAliCloudAuthEvent
|
||||
| AddIdentityAliCloudAuthEvent
|
||||
| UpdateIdentityAliCloudAuthEvent
|
||||
| GetIdentityAliCloudAuthEvent
|
||||
| DeleteIdentityAliCloudAuthEvent
|
||||
| LoginIdentityOciAuthEvent
|
||||
| AddIdentityOciAuthEvent
|
||||
| UpdateIdentityOciAuthEvent
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum ApiDocsTags {
|
||||
TokenAuth = "Token Auth",
|
||||
UniversalAuth = "Universal Auth",
|
||||
GcpAuth = "GCP Auth",
|
||||
AliCloudAuth = "Alibaba Cloud Auth",
|
||||
AwsAuth = "AWS Auth",
|
||||
OciAuth = "OCI Auth",
|
||||
AzureAuth = "Azure Auth",
|
||||
@@ -243,6 +244,43 @@ export const LDAP_AUTH = {
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ALICLOUD_AUTH = {
|
||||
LOGIN: {
|
||||
identityId: "The ID of the identity to login.",
|
||||
Action: "The Alibaba Cloud API action. For STS GetCallerIdentity, this should be 'GetCallerIdentity'.",
|
||||
Format: "The response format. For STS GetCallerIdentity, this should be 'JSON'.",
|
||||
Version: "The API version. This should be in 'YYYY-MM-DD' format (e.g., '2015-04-01').",
|
||||
AccessKeyId: "The AccessKey ID of the RAM user or STS token.",
|
||||
SignatureMethod: "The signature algorithm. For STS GetCallerIdentity, this should be 'HMAC-SHA1'.",
|
||||
Timestamp: "The timestamp of the request in UTC, formatted as 'YYYY-MM-DDTHH:mm:ssZ'.",
|
||||
SignatureVersion: "The signature version. For STS GetCallerIdentity, this should be '1.0'.",
|
||||
SignatureNonce: "A unique random string to prevent replay attacks.",
|
||||
Signature: "The signature string calculated based on the request parameters and AccessKey Secret."
|
||||
},
|
||||
ATTACH: {
|
||||
identityId: "The ID of the identity to attach the configuration onto.",
|
||||
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
|
||||
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||
},
|
||||
UPDATE: {
|
||||
identityId: "The ID of the identity to update the auth method for.",
|
||||
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
|
||||
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||
},
|
||||
RETRIEVE: {
|
||||
identityId: "The ID of the identity to retrieve the auth method for."
|
||||
},
|
||||
REVOKE: {
|
||||
identityId: "The ID of the identity to revoke the auth method for."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const AWS_AUTH = {
|
||||
LOGIN: {
|
||||
identityId: "The ID of the identity to login.",
|
||||
|
||||
@@ -172,6 +172,8 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
||||
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
||||
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
|
||||
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
|
||||
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
|
||||
@@ -383,6 +385,7 @@ export const registerRoutes = async (
|
||||
const identityUaDAL = identityUaDALFactory(db);
|
||||
const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db);
|
||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||
const identityAliCloudAuthDAL = identityAliCloudAuthDALFactory(db);
|
||||
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
|
||||
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
|
||||
const identityOciAuthDAL = identityOciAuthDALFactory(db);
|
||||
@@ -1482,6 +1485,14 @@ export const registerRoutes = async (
|
||||
licenseService
|
||||
});
|
||||
|
||||
const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({
|
||||
identityAccessTokenDAL,
|
||||
identityAliCloudAuthDAL,
|
||||
identityOrgMembershipDAL,
|
||||
licenseService,
|
||||
permissionService
|
||||
});
|
||||
|
||||
const identityAwsAuthService = identityAwsAuthServiceFactory({
|
||||
identityAccessTokenDAL,
|
||||
identityAwsAuthDAL,
|
||||
@@ -1931,6 +1942,7 @@ export const registerRoutes = async (
|
||||
identityUa: identityUaService,
|
||||
identityKubernetesAuth: identityKubernetesAuthService,
|
||||
identityGcpAuth: identityGcpAuthService,
|
||||
identityAliCloudAuth: identityAliCloudAuthService,
|
||||
identityAwsAuth: identityAwsAuthService,
|
||||
identityAzureAuth: identityAzureAuthService,
|
||||
identityOciAuth: identityOciAuthService,
|
||||
|
||||
381
backend/src/server/routes/v1/identity-alicloud-auth-router.ts
Normal file
381
backend/src/server/routes/v1/identity-alicloud-auth-router.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IdentityAlicloudAuthsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ALICLOUD_AUTH, ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||
import { validateArns } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-validators";
|
||||
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||
|
||||
export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/alicloud-auth/login",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AliCloudAuth],
|
||||
description: "Login with Alibaba Cloud Auth",
|
||||
body: z.object({
|
||||
identityId: z.string().trim().describe(ALICLOUD_AUTH.LOGIN.identityId),
|
||||
Action: z.enum(["GetCallerIdentity"]).describe(ALICLOUD_AUTH.LOGIN.Action),
|
||||
Format: z.enum(["JSON"]).describe(ALICLOUD_AUTH.LOGIN.Format),
|
||||
Version: z
|
||||
.string()
|
||||
.refine((val) => new RE2("^\\d{4}-\\d{2}-\\d{2}$").test(val), {
|
||||
message: "Version must be in YYYY-MM-DD format"
|
||||
})
|
||||
.describe(ALICLOUD_AUTH.LOGIN.Version),
|
||||
AccessKeyId: z
|
||||
.string()
|
||||
.refine((val) => new RE2("^[A-Za-z0-9]+$").test(val), {
|
||||
message: "AccessKeyId must be alphanumeric"
|
||||
})
|
||||
.describe(ALICLOUD_AUTH.LOGIN.AccessKeyId),
|
||||
SignatureMethod: z.enum(["HMAC-SHA1"]).describe(ALICLOUD_AUTH.LOGIN.SignatureMethod),
|
||||
Timestamp: z
|
||||
.string()
|
||||
.datetime({
|
||||
message: "Timestamp must be in YYYY-MM-DDTHH:mm:ssZ format"
|
||||
})
|
||||
.refine((val) => val.endsWith("Z"), {
|
||||
message: "Timestamp must be in YYYY-MM-DDTHH:mm:ssZ format"
|
||||
})
|
||||
.describe(ALICLOUD_AUTH.LOGIN.Timestamp),
|
||||
SignatureVersion: z.enum(["1.0"]).describe(ALICLOUD_AUTH.LOGIN.SignatureVersion),
|
||||
SignatureNonce: z
|
||||
.string()
|
||||
.refine((val) => new RE2("^[a-zA-Z0-9-_.]+$").test(val), {
|
||||
message:
|
||||
"SignatureNonce must be at least 1 character long and contain only URL-safe characters (alphanumeric, -, _, .)"
|
||||
})
|
||||
.describe(ALICLOUD_AUTH.LOGIN.SignatureNonce),
|
||||
Signature: z
|
||||
.string()
|
||||
.refine((val) => new RE2("^[A-Za-z0-9+/=]+$").test(val), {
|
||||
message: "Signature must be base64 characters"
|
||||
})
|
||||
.describe(ALICLOUD_AUTH.LOGIN.Signature)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
accessToken: z.string(),
|
||||
expiresIn: z.coerce.number(),
|
||||
accessTokenMaxTTL: z.coerce.number(),
|
||||
tokenType: z.literal("Bearer")
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { identityAliCloudAuth, accessToken, identityAccessToken, identityMembershipOrg } =
|
||||
await server.services.identityAliCloudAuth.login(req.body);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: identityMembershipOrg?.orgId,
|
||||
event: {
|
||||
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
|
||||
metadata: {
|
||||
identityId: identityAliCloudAuth.identityId,
|
||||
identityAccessTokenId: identityAccessToken.id,
|
||||
identityAliCloudAuthId: identityAliCloudAuth.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
tokenType: "Bearer" as const,
|
||||
expiresIn: identityAliCloudAuth.accessTokenTTL,
|
||||
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/alicloud-auth/identities/:identityId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AliCloudAuth],
|
||||
description: "Attach Alibaba Cloud Auth configuration onto identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().trim().describe(ALICLOUD_AUTH.ATTACH.identityId)
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
allowedArns: validateArns.describe(ALICLOUD_AUTH.ATTACH.allowedArns),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||
.describe(ALICLOUD_AUTH.ATTACH.accessTokenTrustedIps),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(315360000)
|
||||
.default(2592000)
|
||||
.describe(ALICLOUD_AUTH.ATTACH.accessTokenTTL),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(315360000)
|
||||
.default(2592000)
|
||||
.describe(ALICLOUD_AUTH.ATTACH.accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.default(0)
|
||||
.describe(ALICLOUD_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||
})
|
||||
.refine(
|
||||
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const identityAliCloudAuth = await server.services.identityAliCloudAuth.attachAliCloudAuth({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
identityId: req.params.identityId,
|
||||
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: identityAliCloudAuth.orgId,
|
||||
event: {
|
||||
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH,
|
||||
metadata: {
|
||||
identityId: identityAliCloudAuth.identityId,
|
||||
allowedArns: identityAliCloudAuth.allowedArns,
|
||||
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||
accessTokenTrustedIps: identityAliCloudAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { identityAliCloudAuth };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/alicloud-auth/identities/:identityId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AliCloudAuth],
|
||||
description: "Update Alibaba Cloud Auth configuration on identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(ALICLOUD_AUTH.UPDATE.identityId)
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
allowedArns: validateArns.describe(ALICLOUD_AUTH.UPDATE.allowedArns),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(ALICLOUD_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(315360000)
|
||||
.optional()
|
||||
.describe(ALICLOUD_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe(ALICLOUD_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.max(315360000)
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe(ALICLOUD_AUTH.UPDATE.accessTokenMaxTTL)
|
||||
})
|
||||
.refine(
|
||||
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||
),
|
||||
response: {
|
||||
200: z.object({
|
||||
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const identityAliCloudAuth = await server.services.identityAliCloudAuth.updateAliCloudAuth({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
identityId: req.params.identityId,
|
||||
allowedArns: req.body.allowedArns
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: identityAliCloudAuth.orgId,
|
||||
event: {
|
||||
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH,
|
||||
metadata: {
|
||||
identityId: identityAliCloudAuth.identityId,
|
||||
allowedArns: identityAliCloudAuth.allowedArns,
|
||||
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||
accessTokenTrustedIps: identityAliCloudAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { identityAliCloudAuth };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/alicloud-auth/identities/:identityId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AliCloudAuth],
|
||||
description: "Retrieve Alibaba Cloud Auth configuration on identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(ALICLOUD_AUTH.RETRIEVE.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const identityAliCloudAuth = await server.services.identityAliCloudAuth.getAliCloudAuth({
|
||||
identityId: req.params.identityId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: identityAliCloudAuth.orgId,
|
||||
event: {
|
||||
type: EventType.GET_IDENTITY_ALICLOUD_AUTH,
|
||||
metadata: {
|
||||
identityId: identityAliCloudAuth.identityId
|
||||
}
|
||||
}
|
||||
});
|
||||
return { identityAliCloudAuth };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/alicloud-auth/identities/:identityId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AliCloudAuth],
|
||||
description: "Delete Alibaba Cloud Auth configuration on identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(ALICLOUD_AUTH.REVOKE.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const identityAliCloudAuth = await server.services.identityAliCloudAuth.revokeIdentityAliCloudAuth({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
identityId: req.params.identityId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: identityAliCloudAuth.orgId,
|
||||
event: {
|
||||
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH,
|
||||
metadata: {
|
||||
identityId: identityAliCloudAuth.identityId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { identityAliCloudAuth };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -15,6 +15,7 @@ import { registerCertRouter } from "./certificate-router";
|
||||
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
||||
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
|
||||
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
||||
import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router";
|
||||
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
||||
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
||||
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
||||
@@ -63,6 +64,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
await authRouter.register(registerIdentityKubernetesRouter);
|
||||
await authRouter.register(registerIdentityGcpAuthRouter);
|
||||
await authRouter.register(registerIdentityAccessTokenRouter);
|
||||
await authRouter.register(registerIdentityAliCloudAuthRouter);
|
||||
await authRouter.register(registerIdentityAwsAuthRouter);
|
||||
await authRouter.register(registerIdentityAzureAuthRouter);
|
||||
await authRouter.register(registerIdentityOciAuthRouter);
|
||||
|
||||
@@ -28,6 +28,11 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
`${TableName.IdentityUniversalAuth}.id`
|
||||
)
|
||||
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||
.leftJoin(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||
@@ -44,6 +49,10 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
.select(
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"),
|
||||
db
|
||||
.ref("accessTokenTrustedIps")
|
||||
.withSchema(TableName.IdentityAliCloudAuth)
|
||||
.as("accessTokenTrustedIpsAliCloud"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
||||
@@ -62,6 +71,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
||||
...doc,
|
||||
trustedIpsUniversalAuth: doc.accessTokenTrustedIpsUa,
|
||||
trustedIpsGcpAuth: doc.accessTokenTrustedIpsGcp,
|
||||
trustedIpsAliCloudAuth: doc.accessTokenTrustedIpsAliCloud,
|
||||
trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws,
|
||||
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
|
||||
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
||||
|
||||
@@ -193,6 +193,7 @@ export const identityAccessTokenServiceFactory = ({
|
||||
const trustedIpsMap: Record<IdentityAuthMethod, unknown> = {
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth,
|
||||
[IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth,
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: identityAccessToken.trustedIpsAliCloudAuth,
|
||||
[IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth,
|
||||
[IdentityAuthMethod.OCI_AUTH]: identityAccessToken.trustedIpsOciAuth,
|
||||
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TIdentityAliCloudAuthDALFactory = ReturnType<typeof identityAliCloudAuthDALFactory>;
|
||||
|
||||
export const identityAliCloudAuthDALFactory = (db: TDbClient) => {
|
||||
return ormify(db, TableName.IdentityAliCloudAuth);
|
||||
};
|
||||
@@ -0,0 +1,361 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { AxiosError } from "axios";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import {
|
||||
constructPermissionErrorMessage,
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||
import { TIdentityAliCloudAuthDALFactory } from "./identity-alicloud-auth-dal";
|
||||
import {
|
||||
TAliCloudGetUserResponse,
|
||||
TAttachAliCloudAuthDTO,
|
||||
TGetAliCloudAuthDTO,
|
||||
TLoginAliCloudAuthDTO,
|
||||
TRevokeAliCloudAuthDTO,
|
||||
TUpdateAliCloudAuthDTO
|
||||
} from "./identity-alicloud-auth-types";
|
||||
|
||||
type TIdentityAliCloudAuthServiceFactoryDep = {
|
||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||
identityAliCloudAuthDAL: Pick<
|
||||
TIdentityAliCloudAuthDALFactory,
|
||||
"findOne" | "transaction" | "create" | "updateById" | "delete"
|
||||
>;
|
||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
};
|
||||
|
||||
export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>;
|
||||
|
||||
export const identityAliCloudAuthServiceFactory = ({
|
||||
identityAccessTokenDAL,
|
||||
identityAliCloudAuthDAL,
|
||||
identityOrgMembershipDAL,
|
||||
licenseService,
|
||||
permissionService
|
||||
}: TIdentityAliCloudAuthServiceFactoryDep) => {
|
||||
const login = async ({ identityId, ...params }: TLoginAliCloudAuthDTO) => {
|
||||
const identityAliCloudAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||
if (!identityAliCloudAuth) {
|
||||
throw new NotFoundError({
|
||||
message: "Alibaba Cloud auth method not found for identity, did you configure Alibaba Cloud auth?"
|
||||
});
|
||||
}
|
||||
|
||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({
|
||||
identityId: identityAliCloudAuth.identityId
|
||||
});
|
||||
|
||||
const requestUrl = new URL("https://sts.aliyuncs.com");
|
||||
|
||||
for (const key of Object.keys(params)) {
|
||||
requestUrl.searchParams.set(key, (params as Record<string, string>)[key]);
|
||||
}
|
||||
|
||||
const { data } = await request.get<TAliCloudGetUserResponse>(requestUrl.toString()).catch((err: AxiosError) => {
|
||||
logger.error(err.response, "AliCloudIdentityLogin: Failed to authenticate with Alibaba Cloud");
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (identityAliCloudAuth.allowedArns) {
|
||||
// In the future we could do partial checks for role ARNs
|
||||
const isAccountAllowed = identityAliCloudAuth.allowedArns.split(",").some((arn) => arn.trim() === data.Arn);
|
||||
|
||||
if (!isAccountAllowed)
|
||||
throw new UnauthorizedError({
|
||||
message: "Access denied: Alibaba Cloud account ARN not allowed."
|
||||
});
|
||||
}
|
||||
|
||||
// Generate the token
|
||||
const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||
const newToken = await identityAccessTokenDAL.create(
|
||||
{
|
||||
identityId: identityAliCloudAuth.identityId,
|
||||
isAccessTokenRevoked: false,
|
||||
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||
accessTokenNumUses: 0,
|
||||
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit,
|
||||
authMethod: IdentityAuthMethod.ALICLOUD_AUTH
|
||||
},
|
||||
tx
|
||||
);
|
||||
return newToken;
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
const accessToken = jwt.sign(
|
||||
{
|
||||
identityId: identityAliCloudAuth.identityId,
|
||||
identityAccessTokenId: identityAccessToken.id,
|
||||
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||
} as TIdentityAccessTokenJwtPayload,
|
||||
appCfg.AUTH_SECRET,
|
||||
Number(identityAccessToken.accessTokenTTL) === 0
|
||||
? undefined
|
||||
: {
|
||||
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
identityAliCloudAuth,
|
||||
accessToken,
|
||||
identityAccessToken,
|
||||
identityMembershipOrg
|
||||
};
|
||||
};
|
||||
|
||||
const attachAliCloudAuth = async ({
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId,
|
||||
isActorSuperAdmin
|
||||
}: TAttachAliCloudAuthDTO) => {
|
||||
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||
|
||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
|
||||
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to add Alibaba Cloud Auth to already configured identity"
|
||||
});
|
||||
}
|
||||
|
||||
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityMembershipOrg.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
!plan.ipAllowlisting &&
|
||||
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||
)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||
throw new BadRequestError({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||
});
|
||||
|
||||
const identityAliCloudAuth = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||
const doc = await identityAliCloudAuthDAL.create(
|
||||
{
|
||||
identityId: identityMembershipOrg.identityId,
|
||||
type: "iam",
|
||||
allowedArns,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||
},
|
||||
tx
|
||||
);
|
||||
return doc;
|
||||
});
|
||||
return { ...identityAliCloudAuth, orgId: identityMembershipOrg.orgId };
|
||||
};
|
||||
|
||||
const updateAliCloudAuth = async ({
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId
|
||||
}: TUpdateAliCloudAuthDTO) => {
|
||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||
throw new NotFoundError({
|
||||
message: "The identity does not have Alibaba Cloud Auth attached"
|
||||
});
|
||||
}
|
||||
|
||||
const identityAliCloudAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||
|
||||
if (
|
||||
(accessTokenMaxTTL || identityAliCloudAuth.accessTokenMaxTTL) > 0 &&
|
||||
(accessTokenTTL || identityAliCloudAuth.accessTokenTTL) >
|
||||
(accessTokenMaxTTL || identityAliCloudAuth.accessTokenMaxTTL)
|
||||
) {
|
||||
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityMembershipOrg.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||
if (
|
||||
!plan.ipAllowlisting &&
|
||||
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||
)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||
throw new BadRequestError({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||
});
|
||||
|
||||
const updatedAliCloudAuth = await identityAliCloudAuthDAL.updateById(identityAliCloudAuth.id, {
|
||||
allowedArns,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||
: undefined
|
||||
});
|
||||
|
||||
return { ...updatedAliCloudAuth, orgId: identityMembershipOrg.orgId };
|
||||
};
|
||||
|
||||
const getAliCloudAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAliCloudAuthDTO) => {
|
||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||
throw new BadRequestError({
|
||||
message: "The identity does not have Alibaba Cloud Auth attached"
|
||||
});
|
||||
}
|
||||
|
||||
const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityMembershipOrg.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||
return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.orgId };
|
||||
};
|
||||
|
||||
const revokeIdentityAliCloudAuth = async ({
|
||||
identityId,
|
||||
actorId,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TRevokeAliCloudAuthDTO) => {
|
||||
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||
throw new BadRequestError({
|
||||
message: "The identity does not have Alibaba Cloud auth"
|
||||
});
|
||||
}
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
identityMembershipOrg.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||
|
||||
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||
ActorType.IDENTITY,
|
||||
identityMembershipOrg.identityId,
|
||||
identityMembershipOrg.orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to revoke Alibaba Cloud auth of identity with more privileged role",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.RevokeAuth,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const revokedIdentityAliCloudAuth = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||
const deletedAliCloudAuth = await identityAliCloudAuthDAL.delete({ identityId }, tx);
|
||||
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.ALICLOUD_AUTH }, tx);
|
||||
|
||||
return { ...deletedAliCloudAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||
});
|
||||
return revokedIdentityAliCloudAuth;
|
||||
};
|
||||
|
||||
return {
|
||||
login,
|
||||
attachAliCloudAuth,
|
||||
updateAliCloudAuth,
|
||||
getAliCloudAuth,
|
||||
revokeIdentityAliCloudAuth
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export type TLoginAliCloudAuthDTO = {
|
||||
identityId: string;
|
||||
Action: string;
|
||||
Format: string;
|
||||
Version: string;
|
||||
AccessKeyId: string;
|
||||
SignatureMethod: string;
|
||||
Timestamp: string;
|
||||
SignatureVersion: string;
|
||||
SignatureNonce: string;
|
||||
Signature: string;
|
||||
};
|
||||
|
||||
export type TAttachAliCloudAuthDTO = {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: { ipAddress: string }[];
|
||||
isActorSuperAdmin?: boolean;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateAliCloudAuthDTO = {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TGetAliCloudAuthDTO = {
|
||||
identityId: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TRevokeAliCloudAuthDTO = {
|
||||
identityId: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TAliCloudGetUserResponse = {
|
||||
Arn: string;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
const arnSchema = z
|
||||
.string()
|
||||
.refine(
|
||||
(val) => new RE2("^acs:ram::[0-9]{16}:(user|role)/.*$").test(val),
|
||||
"Invalid ARN format. Expected format: acs:ram::[0-9]{16}:(user|role)/*"
|
||||
);
|
||||
export const validateArns = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Allowed ARNs required")
|
||||
.max(500, "Input exceeds the maximum limit of 500 characters")
|
||||
.transform((val) => {
|
||||
if (!val) return [];
|
||||
return val
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
})
|
||||
.refine((arr) => arr.every((name) => arnSchema.safeParse(name).success), {
|
||||
message: "One or more ARNs are invalid"
|
||||
})
|
||||
.transform((arr) => arr.join(", "));
|
||||
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TIdentities,
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAzureAuths,
|
||||
TIdentityGcpAuths,
|
||||
@@ -57,6 +58,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
`${TableName.IdentityProjectMembership}.identityId`,
|
||||
`${TableName.IdentityGcpAuth}.identityId`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.IdentityProjectMembership}.identityId`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.IdentityAwsAuth,
|
||||
`${TableName.IdentityProjectMembership}.identityId`,
|
||||
@@ -111,6 +117,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project),
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
@@ -267,6 +274,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityGcpAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAlicloudAuths>(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAwsAuths>(
|
||||
TableName.IdentityAwsAuth,
|
||||
`${TableName.Identity}.id`,
|
||||
@@ -319,6 +331,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
@@ -346,6 +359,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
identityId,
|
||||
identityName,
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
@@ -367,6 +381,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
||||
name: identityName,
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
||||
export const buildAuthMethods = ({
|
||||
uaId,
|
||||
gcpId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
kubernetesId,
|
||||
ociId,
|
||||
@@ -14,6 +15,7 @@ export const buildAuthMethods = ({
|
||||
}: {
|
||||
uaId?: string;
|
||||
gcpId?: string;
|
||||
alicloudId?: string;
|
||||
awsId?: string;
|
||||
kubernetesId?: string;
|
||||
ociId?: string;
|
||||
@@ -26,6 +28,7 @@ export const buildAuthMethods = ({
|
||||
return [
|
||||
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
|
||||
...[gcpId ? IdentityAuthMethod.GCP_AUTH : null],
|
||||
...[alicloudId ? IdentityAuthMethod.ALICLOUD_AUTH : null],
|
||||
...[awsId ? IdentityAuthMethod.AWS_AUTH : null],
|
||||
...[kubernetesId ? IdentityAuthMethod.KUBERNETES_AUTH : null],
|
||||
...[ociId ? IdentityAuthMethod.OCI_AUTH : null],
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
||||
import { TDbClient } from "@app/db";
|
||||
import {
|
||||
TableName,
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAzureAuths,
|
||||
TIdentityGcpAuths,
|
||||
@@ -53,6 +54,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
`${TableName.IdentityOrgMembership}.identityId`,
|
||||
`${TableName.IdentityGcpAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAlicloudAuths>(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
`${TableName.IdentityOrgMembership}.identityId`,
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAwsAuths>(
|
||||
TableName.IdentityAwsAuth,
|
||||
`${TableName.IdentityOrgMembership}.identityId`,
|
||||
@@ -99,6 +105,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
@@ -183,6 +190,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
"paginatedIdentity.identityId",
|
||||
`${TableName.IdentityGcpAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAlicloudAuths>(
|
||||
TableName.IdentityAliCloudAuth,
|
||||
"paginatedIdentity.identityId",
|
||||
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||
)
|
||||
.leftJoin<TIdentityAwsAuths>(
|
||||
TableName.IdentityAwsAuth,
|
||||
"paginatedIdentity.identityId",
|
||||
@@ -236,6 +248,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
@@ -278,6 +291,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
id,
|
||||
orgId,
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
jwtId,
|
||||
@@ -312,6 +326,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
name: identityName,
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
@@ -459,6 +474,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
|
||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||
@@ -502,6 +518,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
total_count,
|
||||
id,
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
jwtId,
|
||||
@@ -536,6 +553,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
||||
name: identityName,
|
||||
authMethods: buildAuthMethods({
|
||||
uaId,
|
||||
alicloudId,
|
||||
awsId,
|
||||
gcpId,
|
||||
kubernetesId,
|
||||
|
||||
4
docs/api-reference/endpoints/alicloud-auth/attach.mdx
Normal file
4
docs/api-reference/endpoints/alicloud-auth/attach.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Attach"
|
||||
openapi: "POST /api/v1/auth/alicloud-auth/identities/{identityId}"
|
||||
---
|
||||
4
docs/api-reference/endpoints/alicloud-auth/login.mdx
Normal file
4
docs/api-reference/endpoints/alicloud-auth/login.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Login"
|
||||
openapi: "POST /api/v1/auth/alicloud-auth/login"
|
||||
---
|
||||
4
docs/api-reference/endpoints/alicloud-auth/retrieve.mdx
Normal file
4
docs/api-reference/endpoints/alicloud-auth/retrieve.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Retrieve"
|
||||
openapi: "GET /api/v1/auth/alicloud-auth/identities/{identityId}"
|
||||
---
|
||||
4
docs/api-reference/endpoints/alicloud-auth/revoke.mdx
Normal file
4
docs/api-reference/endpoints/alicloud-auth/revoke.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Revoke"
|
||||
openapi: "DELETE /api/v1/auth/alicloud-auth/identities/{identityId}"
|
||||
---
|
||||
4
docs/api-reference/endpoints/alicloud-auth/update.mdx
Normal file
4
docs/api-reference/endpoints/alicloud-auth/update.mdx
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v1/auth/alicloud-auth/identities/{identityId}"
|
||||
---
|
||||
192
docs/documentation/platform/identities/alicloud-auth.mdx
Normal file
192
docs/documentation/platform/identities/alicloud-auth.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Alibaba Cloud Auth
|
||||
description: "Learn how to authenticate with Infisical using Alibaba Cloud user accounts."
|
||||
---
|
||||
|
||||
**Alibaba Cloud Auth** is an authentication method that verifies Alibaba Cloud users through signature validation, allowing secure access to Infisical resources.
|
||||
|
||||
## Diagram
|
||||
|
||||
The following sequence diagram illustrates the Alibaba Cloud Auth workflow for authenticating Alibaba Cloud users with Infisical.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Infisical
|
||||
participant Alibaba Cloud
|
||||
|
||||
Note over Client,Client: Step 1: Sign user identity request
|
||||
|
||||
Note over Client,Infisical: Step 2: Login Operation
|
||||
Client->>Infisical: Send signed request details to /api/v1/auth/alicloud-auth/login
|
||||
|
||||
Note over Infisical,Alibaba Cloud: Step 3: Request verification
|
||||
Infisical->>Alibaba Cloud: Forward signed request
|
||||
Alibaba Cloud-->>Infisical: Return user details
|
||||
|
||||
Note over Infisical: Step 4: Identity property validation
|
||||
Infisical->>Client: Return short-lived access token
|
||||
|
||||
Note over Client,Infisical: Step 5: Access Infisical API with token
|
||||
Client->>Infisical: Make authenticated requests using the short-lived access token
|
||||
```
|
||||
|
||||
## Concept
|
||||
|
||||
At a high level, Infisical authenticates an Alibaba Cloud user by verifying its identity and checking that it meets specific requirements (e.g., its ARN is whitelisted) at the `/api/v1/auth/alicloud-auth/login` endpoint. If successful,
|
||||
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
|
||||
|
||||
To be more specific:
|
||||
1. The client signs a `GetCallerIdentity` request using an Alibaba Cloud user's access key secret; this is done using an HMAC sha1 algorithm.
|
||||
2. The client sends the signed request information alongside the signature to Infisical at the `/api/v1/auth/alicloud-auth/login` endpoint.
|
||||
3. Infisical reconstructs the request and sends it to Alibaba Cloud for verification and obtains the identity associated with the Alibaba Cloud user.
|
||||
4. Infisical checks the user's properties against set criteria such as **Allowed ARNs**.
|
||||
5. If all checks pass, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
In order to sign requests, you must have an Alibaba Cloud user with credentials such as access key ID and secret. If you're unaware of how to create a user and obtain the needed credentials, expand the menu below.
|
||||
|
||||
<Accordion title="Creating an Alibaba Cloud user">
|
||||
<Steps>
|
||||
<Step title="Click 'Create User'">
|
||||
Visit https://ram.console.aliyun.com/users to get to the Users page and click **Create User**.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Provide User Info">
|
||||
Fill out the username and display name with values of your choice and click **OK**.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Click User Row">
|
||||
After a user has been created, click on its row to see user information.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Create Access Key">
|
||||
Click **Create AccessKey** and select the most relevant option for your use-case. Then click **Continue**.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Copy Credentials">
|
||||
Save the displayed credentials for later steps.
|
||||
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
</Accordion>
|
||||
|
||||
## Guide
|
||||
|
||||
In the following steps, we explore how to create and use identities for your workloads and applications on Alibaba Cloud to
|
||||
access the Infisical API using request signing.
|
||||
|
||||
### Creating an identity
|
||||
|
||||
To create an identity, head to your Organization Settings > Access Control > [Identities](https://app.infisical.com/organization/access-management?selectedTab=identities) and press **Create identity**.
|
||||
|
||||

|
||||
|
||||
When creating an identity, you specify an organization-level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > [Organization Roles](https://app.infisical.com/organization/access-management?selectedTab=roles).
|
||||
|
||||

|
||||
|
||||
Input some details for your new identity:
|
||||
- **Name (required):** A friendly name for the identity.
|
||||
- **Role (required):** A role from the [**Organization Roles**](https://app.infisical.com/organization/access-management?selectedTab=roles) tab for the identity to assume. The organization role assigned will determine what organization-level resources this identity can have access to.
|
||||
|
||||
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
|
||||
|
||||

|
||||
|
||||
Since the identity has been configured with [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) by default, you should reconfigure it to use Alibaba Cloud Auth instead. To do this, click the cog next to **Universal Auth** and then select **Delete** in the options dropdown.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Now create a new Alibaba Cloud Auth Method.
|
||||
|
||||

|
||||
|
||||
Here's some information about each field:
|
||||
- **Allowed ARNs:** A comma-separated list of trusted Alibaba Cloud ARNs that are allowed to authenticate with Infisical.
|
||||
- **Access Token TTL (default is `2592000` equivalent to 30 days):** The lifetime for an access token in seconds. This value will be referenced at renewal time.
|
||||
- **Access Token Max TTL (default is `2592000` equivalent to 30 days):** The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.
|
||||
- **Access Token Max Number of Uses (default is `0`):** The maximum number of times that an access token can be used; a value of `0` implies an infinite number of uses.
|
||||
- **Access Token Trusted IPs:** The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
|
||||
|
||||
### Adding an identity to a project
|
||||
|
||||
In order to allow an identity to access project-level resources such as secrets, you must add it to the relevant projects.
|
||||
|
||||
To do this, head over to the project you want to add the identity to and navigate to Project Settings > Access Control > Machine Identities and press **Add Identity**.
|
||||
|
||||

|
||||
|
||||
Select the identity you want to add to the project and the project-level role you want it to assume. The project role given to the identity will determine what project-level resources this identity can access.
|
||||
|
||||

|
||||
|
||||
### Accessing the Infisical API with the identity
|
||||
|
||||
To access the Infisical API as the identity, you need to construct a signed `GetCallerIdentity` request and then make a request to the `/api/v1/auth/alicloud-auth/login` endpoint passing the signed data and signature.
|
||||
|
||||
Below is an example of how you can authenticate with Infisical using NodeJS.
|
||||
|
||||
```ts
|
||||
import crypto from "crypto";
|
||||
|
||||
// We highly recommend using environment variables instead of hardcoding these values
|
||||
const ALICLOUD_ACCESS_KEY_ID = "...";
|
||||
const ALICLOUD_ACCESS_KEY_SECRET = "...";
|
||||
|
||||
const params: { [key: string]: string } = {
|
||||
Action: "GetCallerIdentity",
|
||||
Format: "JSON",
|
||||
Version: "2015-04-01",
|
||||
AccessKeyId: ALICLOUD_ACCESS_KEY_ID,
|
||||
SignatureMethod: "HMAC-SHA1",
|
||||
Timestamp: new Date().toISOString(),
|
||||
SignatureVersion: "1.0",
|
||||
SignatureNonce: crypto.randomBytes(16).toString("hex"),
|
||||
};
|
||||
|
||||
const canonicalizedQueryString = Object.keys(params)
|
||||
.sort()
|
||||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||
.join("&");
|
||||
|
||||
const stringToSign = `GET&%2F&${encodeURIComponent(canonicalizedQueryString)}`;
|
||||
|
||||
const signature = crypto
|
||||
.createHmac("sha1", `${ALICLOUD_ACCESS_KEY_SECRET}&`)
|
||||
.update(stringToSign)
|
||||
.digest("base64");
|
||||
|
||||
const res = await fetch(
|
||||
"https://app.infisical.com/api/v1/auth/alicloud-auth/login",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
identityId: "...", // Replace with your identity ID
|
||||
Signature: signature,
|
||||
...params,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
console.log("Infisical Response:", JSON.stringify(json));
|
||||
```
|
||||
|
||||
<Note>
|
||||
Each identity access token has a time-to-live (TTL) which you can infer from the response of the login operation; the default TTL is `7200` seconds, which can be adjusted.
|
||||
|
||||
If an identity access token expires, it can no longer access the Infisical API. A new access token should be obtained by performing another login operation.
|
||||
</Note>
|
||||
BIN
docs/images/platform/identities/alicloud/create-access-key.png
Normal file
BIN
docs/images/platform/identities/alicloud/create-access-key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 511 KiB |
BIN
docs/images/platform/identities/alicloud/create-auth-method.png
Normal file
BIN
docs/images/platform/identities/alicloud/create-auth-method.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 687 KiB |
BIN
docs/images/platform/identities/alicloud/credentials.png
Normal file
BIN
docs/images/platform/identities/alicloud/credentials.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
docs/images/platform/identities/alicloud/user-info.png
Normal file
BIN
docs/images/platform/identities/alicloud/user-info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 270 KiB |
BIN
docs/images/platform/identities/alicloud/user-row.png
Normal file
BIN
docs/images/platform/identities/alicloud/user-row.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
BIN
docs/images/platform/identities/alicloud/users-page.png
Normal file
BIN
docs/images/platform/identities/alicloud/users-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 301 KiB |
@@ -329,6 +329,7 @@
|
||||
{
|
||||
"group": "Machine Identities",
|
||||
"pages": [
|
||||
"documentation/platform/identities/alicloud-auth",
|
||||
"documentation/platform/identities/aws-auth",
|
||||
"documentation/platform/identities/azure-auth",
|
||||
"documentation/platform/identities/gcp-auth",
|
||||
@@ -727,6 +728,16 @@
|
||||
"api-reference/endpoints/gcp-auth/revoke"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Alibaba Cloud Auth",
|
||||
"pages": [
|
||||
"api-reference/endpoints/alicloud-auth/login",
|
||||
"api-reference/endpoints/alicloud-auth/attach",
|
||||
"api-reference/endpoints/alicloud-auth/retrieve",
|
||||
"api-reference/endpoints/alicloud-auth/update",
|
||||
"api-reference/endpoints/alicloud-auth/revoke"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AWS Auth",
|
||||
"pages": [
|
||||
|
||||
@@ -5,6 +5,7 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
|
||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth",
|
||||
[IdentityAuthMethod.KUBERNETES_AUTH]: "Kubernetes Auth",
|
||||
[IdentityAuthMethod.GCP_AUTH]: "GCP Auth",
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: "Alibaba Cloud Auth",
|
||||
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
|
||||
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
|
||||
[IdentityAuthMethod.OCI_AUTH]: "OCI Auth",
|
||||
|
||||
@@ -3,6 +3,7 @@ export enum IdentityAuthMethod {
|
||||
UNIVERSAL_AUTH = "universal-auth",
|
||||
KUBERNETES_AUTH = "kubernetes-auth",
|
||||
GCP_AUTH = "gcp-auth",
|
||||
ALICLOUD_AUTH = "alicloud-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
OCI_AUTH = "oci-auth",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { apiRequest } from "@app/config/request";
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { identitiesKeys } from "./queries";
|
||||
import {
|
||||
AddIdentityAliCloudAuthDTO,
|
||||
AddIdentityAwsAuthDTO,
|
||||
AddIdentityAzureAuthDTO,
|
||||
AddIdentityGcpAuthDTO,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
CreateIdentityUniversalAuthClientSecretRes,
|
||||
CreateTokenIdentityTokenAuthDTO,
|
||||
CreateTokenIdentityTokenAuthRes,
|
||||
DeleteIdentityAliCloudAuthDTO,
|
||||
DeleteIdentityAwsAuthDTO,
|
||||
DeleteIdentityAzureAuthDTO,
|
||||
DeleteIdentityDTO,
|
||||
@@ -35,6 +37,7 @@ import {
|
||||
DeleteIdentityUniversalAuthDTO,
|
||||
Identity,
|
||||
IdentityAccessToken,
|
||||
IdentityAliCloudAuth,
|
||||
IdentityAwsAuth,
|
||||
IdentityAzureAuth,
|
||||
IdentityGcpAuth,
|
||||
@@ -47,6 +50,7 @@ import {
|
||||
IdentityUniversalAuth,
|
||||
RevokeTokenDTO,
|
||||
RevokeTokenRes,
|
||||
UpdateIdentityAliCloudAuthDTO,
|
||||
UpdateIdentityAwsAuthDTO,
|
||||
UpdateIdentityAzureAuthDTO,
|
||||
UpdateIdentityDTO,
|
||||
@@ -553,6 +557,103 @@ export const useDeleteIdentityOciAuth = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityAliCloudAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityAliCloudAuth, object, AddIdentityAliCloudAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const {
|
||||
data: { identityAliCloudAuth }
|
||||
} = await apiRequest.post<{ identityAliCloudAuth: IdentityAliCloudAuth }>(
|
||||
`/api/v1/auth/alicloud-auth/identities/${identityId}`,
|
||||
{
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityAliCloudAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityAliCloudAuth, object, UpdateIdentityAliCloudAuthDTO>({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}) => {
|
||||
const {
|
||||
data: { identityAliCloudAuth }
|
||||
} = await apiRequest.patch<{ identityAliCloudAuth: IdentityAliCloudAuth }>(
|
||||
`/api/v1/auth/alicloud-auth/identities/${identityId}`,
|
||||
{
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}
|
||||
);
|
||||
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityAliCloudAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityAliCloudAuth, object, DeleteIdentityAliCloudAuthDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const {
|
||||
data: { identityAliCloudAuth }
|
||||
} = await apiRequest.delete(`/api/v1/auth/alicloud-auth/identities/${identityId}`);
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityOidcAuth = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<IdentityOidcAuth, object, UpdateIdentityOidcAuthDTO>({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { TReactQueryOptions } from "@app/types/reactQuery";
|
||||
import {
|
||||
ClientSecretData,
|
||||
IdentityAccessToken,
|
||||
IdentityAliCloudAuth,
|
||||
IdentityAwsAuth,
|
||||
IdentityAzureAuth,
|
||||
IdentityGcpAuth,
|
||||
@@ -33,6 +34,8 @@ export const identitiesKeys = {
|
||||
getIdentityGcpAuth: (identityId: string) => [{ identityId }, "identity-gcp-auth"] as const,
|
||||
getIdentityOidcAuth: (identityId: string) => [{ identityId }, "identity-oidc-auth"] as const,
|
||||
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const,
|
||||
getIdentityAliCloudAuth: (identityId: string) =>
|
||||
[{ identityId }, "identity-alicloud-auth"] as const,
|
||||
getIdentityOciAuth: (identityId: string) => [{ identityId }, "identity-oci-auth"] as const,
|
||||
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
|
||||
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
|
||||
@@ -193,6 +196,27 @@ export const useGetIdentityOciAuth = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityAliCloudAuth = (
|
||||
identityId: string,
|
||||
options?: TReactQueryOptions["options"]
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityAliCloudAuth }
|
||||
} = await apiRequest.get<{ identityAliCloudAuth: IdentityAliCloudAuth }>(
|
||||
`/api/v1/auth/alicloud-auth/identities/${identityId}`
|
||||
);
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
staleTime: 0,
|
||||
gcTime: 0,
|
||||
...options,
|
||||
enabled: Boolean(identityId) && (options?.enabled ?? true)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIdentityAzureAuth = (
|
||||
identityId: string,
|
||||
options?: TReactQueryOptions["options"]
|
||||
|
||||
@@ -293,6 +293,45 @@ export type DeleteIdentityAwsAuthDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityAliCloudAuth = {
|
||||
identityId: string;
|
||||
type: "iam";
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: IdentityTrustedIp[];
|
||||
};
|
||||
|
||||
export type AddIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type DeleteIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type IdentityOciAuth = {
|
||||
identityId: string;
|
||||
type: "iam";
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization, useSubscription } from "@app/context";
|
||||
import {
|
||||
useAddIdentityAliCloudAuth,
|
||||
useGetIdentityAliCloudAuth,
|
||||
useUpdateIdentityAliCloudAuth
|
||||
} from "@app/hooks/api";
|
||||
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityFormTab } from "./types";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
allowedArns: z.string().min(1, "Required"),
|
||||
accessTokenTTL: z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => Number(value) <= 315360000,
|
||||
"Access Token TTL cannot be greater than 315360000"
|
||||
),
|
||||
accessTokenMaxTTL: z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => Number(value) <= 315360000,
|
||||
"Access Token Max TTL cannot be greater than 315360000"
|
||||
),
|
||||
accessTokenNumUsesLimit: z.string(),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().max(50)
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
identityId?: string;
|
||||
isUpdate?: boolean;
|
||||
};
|
||||
|
||||
export const IdentityAliCloudAuthForm = ({
|
||||
handlePopUpOpen,
|
||||
handlePopUpToggle,
|
||||
identityId,
|
||||
isUpdate
|
||||
}: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityAliCloudAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAliCloudAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
|
||||
const { data } = useGetIdentityAliCloudAuth(identityId ?? "", {
|
||||
enabled: isUpdate
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
allowedArns: "",
|
||||
accessTokenTTL: "2592000",
|
||||
accessTokenMaxTTL: "2592000",
|
||||
accessTokenNumUsesLimit: "0",
|
||||
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
fields: accessTokenTrustedIpsFields,
|
||||
append: appendAccessTokenTrustedIp,
|
||||
remove: removeAccessTokenTrustedIp
|
||||
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({
|
||||
allowedArns: data.allowedArns || "",
|
||||
accessTokenTTL: String(data.accessTokenTTL),
|
||||
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
|
||||
({ ipAddress, prefix }: IdentityTrustedIp) => {
|
||||
return {
|
||||
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
|
||||
};
|
||||
}
|
||||
)
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
allowedArns: "",
|
||||
accessTokenTTL: "2592000",
|
||||
accessTokenMaxTTL: "2592000",
|
||||
accessTokenNumUsesLimit: "0",
|
||||
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const onFormSubmit = async ({
|
||||
allowedArns,
|
||||
accessTokenTTL,
|
||||
accessTokenMaxTTL,
|
||||
accessTokenNumUsesLimit,
|
||||
accessTokenTrustedIps
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!identityId) return;
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
allowedArns,
|
||||
identityId,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps
|
||||
});
|
||||
}
|
||||
|
||||
handlePopUpToggle("identityAuthMethod", false);
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch {
|
||||
createNotification({
|
||||
text: `Failed to ${isUpdate ? "update" : "configure"} identity`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit, (fields) => {
|
||||
setTabValue(
|
||||
["accessTokenTrustedIps"].includes(Object.keys(fields)[0])
|
||||
? IdentityFormTab.Advanced
|
||||
: IdentityFormTab.Configuration
|
||||
);
|
||||
})}
|
||||
>
|
||||
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
|
||||
<TabList>
|
||||
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
|
||||
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={IdentityFormTab.Configuration}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="allowedArns"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Allowed ARNs"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="acs:ram::0000000000000000:user/user1, acs:ram::0000000000000000:user/user2, ..."
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="2592000"
|
||||
name="accessTokenTTL"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token TTL (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="2592000"
|
||||
name="accessTokenMaxTTL"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token Max TTL (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="0"
|
||||
name="accessTokenNumUsesLimit"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Access Token Max Number of Uses"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="0" type="number" min="0" step="1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={IdentityFormTab.Advanced}>
|
||||
{accessTokenTrustedIpsFields.map(({ id }, index) => (
|
||||
<div className="mb-3 flex items-end space-x-2" key={id}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`accessTokenTrustedIps.${index}.ipAddress`}
|
||||
defaultValue="0.0.0.0/0"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className="mb-0 flex-grow"
|
||||
label={index === 0 ? "Access Token Trusted IPs" : undefined}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
field.onChange(e);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
placeholder="123.456.789.0"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
removeAccessTokenTrustedIp(index);
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="p-3"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
))}
|
||||
<div className="my-4 ml-1">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
appendAccessTokenTrustedIp({
|
||||
ipAddress: "0.0.0.0/0"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
size="xs"
|
||||
>
|
||||
Add IP Address
|
||||
</Button>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{isUpdate ? "Update" : "Add"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import { Badge, FormControl, Select, SelectItem, Tooltip } from "@app/components
|
||||
import { IdentityAuthMethod } from "@app/hooks/api/identities";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityAliCloudAuthForm } from "./IdentityAliCloudAuthForm";
|
||||
import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm";
|
||||
import { IdentityAzureAuthForm } from "./IdentityAzureAuthForm";
|
||||
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
|
||||
@@ -45,6 +46,7 @@ const identityAuthMethods = [
|
||||
{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH },
|
||||
{ label: "Kubernetes Auth", value: IdentityAuthMethod.KUBERNETES_AUTH },
|
||||
{ label: "GCP Auth", value: IdentityAuthMethod.GCP_AUTH },
|
||||
{ label: "Alibaba Cloud Auth", value: IdentityAuthMethod.ALICLOUD_AUTH },
|
||||
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH },
|
||||
{ label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH },
|
||||
{ label: "OCI Auth", value: IdentityAuthMethod.OCI_AUTH },
|
||||
@@ -172,6 +174,16 @@ export const IdentityAuthMethodModalContent = ({
|
||||
)
|
||||
},
|
||||
|
||||
[IdentityAuthMethod.ALICLOUD_AUTH]: {
|
||||
render: () => (
|
||||
<IdentityAliCloudAuthForm
|
||||
identityId={identityAuthMethodData.identityId}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
[IdentityAuthMethod.AWS_AUTH]: {
|
||||
render: () => (
|
||||
<IdentityAwsAuthForm
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { useGetIdentityAliCloudAuth } from "@app/hooks/api";
|
||||
import { IdentityAliCloudAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm";
|
||||
|
||||
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
import { ViewIdentityContentWrapper } from "./ViewIdentityContentWrapper";
|
||||
|
||||
export const ViewIdentityAliCloudAuthContent = ({
|
||||
identityId,
|
||||
handlePopUpToggle,
|
||||
handlePopUpOpen,
|
||||
onDelete,
|
||||
popUp
|
||||
}: ViewAuthMethodProps) => {
|
||||
const { data, isPending } = useGetIdentityAliCloudAuth(identityId);
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<Spinner className="text-mineshaft-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<EmptyState
|
||||
icon={faBan}
|
||||
title="Could not find Alibaba Cloud Auth associated with this Identity."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (popUp.identityAuthMethod.isOpen) {
|
||||
return (
|
||||
<IdentityAliCloudAuthForm
|
||||
identityId={identityId}
|
||||
isUpdate
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewIdentityContentWrapper
|
||||
onEdit={() => handlePopUpOpen("identityAuthMethod")}
|
||||
onDelete={onDelete}
|
||||
>
|
||||
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
|
||||
{data.accessTokenTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
|
||||
{data.accessTokenMaxTTL}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
|
||||
{data.accessTokenNumUsesLimit}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
|
||||
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
|
||||
</IdentityAuthFieldDisplay>
|
||||
<IdentityAuthFieldDisplay className="col-span-2" label="Allowed ARNs">
|
||||
{data.allowedArns
|
||||
?.split(",")
|
||||
.map((u) => u.trim())
|
||||
.join(", ")}
|
||||
</IdentityAuthFieldDisplay>
|
||||
</ViewIdentityContentWrapper>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useDeleteIdentityAliCloudAuth,
|
||||
useDeleteIdentityAwsAuth,
|
||||
useDeleteIdentityAzureAuth,
|
||||
useDeleteIdentityGcpAuth,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { ViewAuthMethodProps } from "./types";
|
||||
import { ViewIdentityAliCloudAuthContent } from "./ViewIdentityAliCloudAuthContent";
|
||||
import { ViewIdentityAwsAuthContent } from "./ViewIdentityAwsAuthContent";
|
||||
import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
|
||||
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
|
||||
@@ -63,6 +65,7 @@ export const Content = ({
|
||||
const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth();
|
||||
const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth();
|
||||
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
|
||||
const { mutateAsync: revokeAliCloudAuth } = useDeleteIdentityAliCloudAuth();
|
||||
const { mutateAsync: revokeOciAuth } = useDeleteIdentityOciAuth();
|
||||
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
|
||||
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
|
||||
@@ -102,6 +105,10 @@ export const Content = ({
|
||||
revokeMethod = revokeOciAuth;
|
||||
Component = ViewIdentityOciAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.ALICLOUD_AUTH:
|
||||
revokeMethod = revokeAliCloudAuth;
|
||||
Component = ViewIdentityAliCloudAuthContent;
|
||||
break;
|
||||
case IdentityAuthMethod.OIDC_AUTH:
|
||||
revokeMethod = revokeOidcAuth;
|
||||
Component = ViewIdentityOidcAuthContent;
|
||||
|
||||
Reference in New Issue
Block a user