Merge pull request #1999 from akhilmhdh/feat/ui-permission-check-broken

Terraform identity management apis
This commit is contained in:
Akhil Mohan
2024-06-25 22:46:53 +05:30
committed by GitHub
27 changed files with 878 additions and 59 deletions

View File

@@ -65,25 +65,31 @@ export enum EventType {
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth", ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth", UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth", GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
REVOKE_IDENTITY_UNIVERSAL_AUTH = "revoke-identity-universal-auth",
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth", LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth", ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth", UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth", GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret", CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret", REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret", GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth", LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth", ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth", UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth", GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth", LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth", ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth", UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth", GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth", LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth", ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth", UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth", GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
CREATE_ENVIRONMENT = "create-environment", CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
@@ -434,6 +440,13 @@ interface GetIdentityUniversalAuthEvent {
}; };
} }
interface DeleteIdentityUniversalAuthEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityKubernetesAuthEvent { interface LoginIdentityKubernetesAuthEvent {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH; type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH;
metadata: { metadata: {
@@ -457,6 +470,13 @@ interface AddIdentityKubernetesAuthEvent {
}; };
} }
interface DeleteIdentityKubernetesAuthEvent {
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityKubernetesAuthEvent { interface UpdateIdentityKubernetesAuthEvent {
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH; type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH;
metadata: { metadata: {
@@ -493,6 +513,14 @@ interface GetIdentityUniversalAuthClientSecretsEvent {
}; };
} }
interface GetIdentityUniversalAuthClientSecretByIdEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID;
metadata: {
identityId: string;
clientSecretId: string;
};
}
interface RevokeIdentityUniversalAuthClientSecretEvent { interface RevokeIdentityUniversalAuthClientSecretEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET; type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
metadata: { metadata: {
@@ -525,6 +553,13 @@ interface AddIdentityGcpAuthEvent {
}; };
} }
interface DeleteIdentityGcpAuthEvent {
type: EventType.REVOKE_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityGcpAuthEvent { interface UpdateIdentityGcpAuthEvent {
type: EventType.UPDATE_IDENTITY_GCP_AUTH; type: EventType.UPDATE_IDENTITY_GCP_AUTH;
metadata: { metadata: {
@@ -570,6 +605,13 @@ interface AddIdentityAwsAuthEvent {
}; };
} }
interface DeleteIdentityAwsAuthEvent {
type: EventType.REVOKE_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityAwsAuthEvent { interface UpdateIdentityAwsAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_AUTH; type: EventType.UPDATE_IDENTITY_AWS_AUTH;
metadata: { metadata: {
@@ -613,6 +655,13 @@ interface AddIdentityAzureAuthEvent {
}; };
} }
interface DeleteIdentityAzureAuthEvent {
type: EventType.REVOKE_IDENTITY_AZURE_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityAzureAuthEvent { interface UpdateIdentityAzureAuthEvent {
type: EventType.UPDATE_IDENTITY_AZURE_AUTH; type: EventType.UPDATE_IDENTITY_AZURE_AUTH;
metadata: { metadata: {
@@ -1003,24 +1052,30 @@ export type Event =
| LoginIdentityUniversalAuthEvent | LoginIdentityUniversalAuthEvent
| AddIdentityUniversalAuthEvent | AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent | UpdateIdentityUniversalAuthEvent
| DeleteIdentityUniversalAuthEvent
| GetIdentityUniversalAuthEvent | GetIdentityUniversalAuthEvent
| LoginIdentityKubernetesAuthEvent | LoginIdentityKubernetesAuthEvent
| DeleteIdentityKubernetesAuthEvent
| AddIdentityKubernetesAuthEvent | AddIdentityKubernetesAuthEvent
| UpdateIdentityKubernetesAuthEvent | UpdateIdentityKubernetesAuthEvent
| GetIdentityKubernetesAuthEvent | GetIdentityKubernetesAuthEvent
| CreateIdentityUniversalAuthClientSecretEvent | CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent | GetIdentityUniversalAuthClientSecretsEvent
| GetIdentityUniversalAuthClientSecretByIdEvent
| RevokeIdentityUniversalAuthClientSecretEvent | RevokeIdentityUniversalAuthClientSecretEvent
| LoginIdentityGcpAuthEvent | LoginIdentityGcpAuthEvent
| AddIdentityGcpAuthEvent | AddIdentityGcpAuthEvent
| DeleteIdentityGcpAuthEvent
| UpdateIdentityGcpAuthEvent | UpdateIdentityGcpAuthEvent
| GetIdentityGcpAuthEvent | GetIdentityGcpAuthEvent
| LoginIdentityAwsAuthEvent | LoginIdentityAwsAuthEvent
| AddIdentityAwsAuthEvent | AddIdentityAwsAuthEvent
| UpdateIdentityAwsAuthEvent | UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent | GetIdentityAwsAuthEvent
| DeleteIdentityAwsAuthEvent
| LoginIdentityAzureAuthEvent | LoginIdentityAzureAuthEvent
| AddIdentityAzureAuthEvent | AddIdentityAzureAuthEvent
| DeleteIdentityAzureAuthEvent
| UpdateIdentityAzureAuthEvent | UpdateIdentityAzureAuthEvent
| GetIdentityAzureAuthEvent | GetIdentityAzureAuthEvent
| CreateEnvironmentEvent | CreateEnvironmentEvent

View File

@@ -42,6 +42,13 @@ export const IDENTITIES = {
}, },
DELETE: { DELETE: {
identityId: "The ID of the identity to delete." identityId: "The ID of the identity to delete."
},
GET_BY_ID: {
identityId: "The ID of the identity to get details.",
orgId: "The ID of the org of the identity"
},
LIST: {
orgId: "The ID of the organization to list identities."
} }
} as const; } as const;
@@ -65,6 +72,9 @@ export const UNIVERSAL_AUTH = {
RETRIEVE: { RETRIEVE: {
identityId: "The ID of the identity to retrieve." identityId: "The ID of the identity to retrieve."
}, },
REVOKE: {
identityId: "The ID of the identity to revoke."
},
UPDATE: { UPDATE: {
identityId: "The ID of the identity to update.", identityId: "The ID of the identity to update.",
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.", clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
@@ -83,6 +93,10 @@ export const UNIVERSAL_AUTH = {
LIST_CLIENT_SECRETS: { LIST_CLIENT_SECRETS: {
identityId: "The ID of the identity to list client secrets for." identityId: "The ID of the identity to list client secrets for."
}, },
GET_CLIENT_SECRET: {
identityId: "The ID of the identity to get the client secret from.",
clientSecretId: "The ID of the client secret to get details."
},
REVOKE_CLIENT_SECRET: { REVOKE_CLIENT_SECRET: {
identityId: "The ID of the identity to revoke the client secret from.", identityId: "The ID of the identity to revoke the client secret from.",
clientSecretId: "The ID of the client secret to revoke." clientSecretId: "The ID of the client secret to revoke."
@@ -104,6 +118,27 @@ export const AWS_AUTH = {
iamRequestBody: iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.", "The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request." iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
},
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const AZURE_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const GCP_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
}
} as const;
export const KUBERNETES_AUTH = {
REVOKE: {
identityId: "The ID of the identity to revoke."
} }
} as const; } as const;

View File

@@ -266,4 +266,51 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
return { identityAwsAuth }; return { identityAwsAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(AWS_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.revokeIdentityAwsAuth({
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: identityAwsAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId
}
}
});
return { identityAwsAuth };
}
});
}; };

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityAzureAuthsSchema } from "@app/db/schemas"; import { IdentityAzureAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { AZURE_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -259,4 +260,51 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
return { identityAzureAuth }; return { identityAzureAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/azure-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Azure Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(AZURE_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityAzureAuth: IdentityAzureAuthsSchema
})
}
},
handler: async (req) => {
const identityAzureAuth = await server.services.identityAzureAuth.revokeIdentityAzureAuth({
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: identityAzureAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_AZURE_AUTH,
metadata: {
identityId: identityAzureAuth.identityId
}
}
});
return { identityAzureAuth };
}
});
}; };

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityGcpAuthsSchema } from "@app/db/schemas"; import { IdentityGcpAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { GCP_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -265,4 +266,51 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
return { identityGcpAuth }; return { identityGcpAuth };
} }
}); });
server.route({
method: "DELETE",
url: "/gcp-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete GCP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(GCP_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityGcpAuth: IdentityGcpAuthsSchema
})
}
},
handler: async (req) => {
const identityGcpAuth = await server.services.identityGcpAuth.revokeIdentityGcpAuth({
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: identityGcpAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId
}
}
});
return { identityGcpAuth };
}
});
}; };

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas"; import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { KUBERNETES_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -280,4 +281,54 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) }; return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
} }
}); });
server.route({
method: "DELETE",
url: "/kubernetes-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Kubernetes Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(KUBERNETES_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.omit({
caCert: true,
tokenReviewerJwt: true
})
})
}
},
handler: async (req) => {
const identityKubernetesAuth = await server.services.identityKubernetesAuth.revokeIdentityKubernetesAuth({
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: identityKubernetesAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_KUBERNETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId
}
}
});
return { identityKubernetesAuth };
}
});
}; };

View File

@@ -1,9 +1,9 @@
import { z } from "zod"; import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas"; import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { IDENTITIES } from "@app/lib/api-docs"; import { IDENTITIES } from "@app/lib/api-docs";
import { creationLimit, writeLimit } from "@app/server/config/rateLimiter"; import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@@ -170,4 +170,94 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
return { identity }; return { identity };
} }
}); });
server.route({
method: "GET",
url: "/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get an identity by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(IDENTITIES.GET_BY_ID.identityId)
}),
response: {
200: z.object({
identity: IdentityOrgMembershipsSchema.extend({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
})
}
},
handler: async (req) => {
const identity = await server.services.identity.getIdentityById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.identityId
});
return { identity };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List identities",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
orgId: z.string().describe(IDENTITIES.LIST.orgId)
}),
response: {
200: z.object({
identities: IdentityOrgMembershipsSchema.extend({
customRole: OrgRolesSchema.pick({
id: true,
name: true,
slug: true,
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
}).array()
})
}
},
handler: async (req) => {
const identities = await server.services.identity.listOrgIdentities({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.query.orgId
});
return { identities };
}
});
}; };

View File

@@ -134,7 +134,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.attachUa({ const identityUniversalAuth = await server.services.identityUa.attachUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
@@ -219,7 +219,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.updateUa({ const identityUniversalAuth = await server.services.identityUa.updateUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
@@ -272,7 +272,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.getIdentityUa({ const identityUniversalAuth = await server.services.identityUa.getIdentityUniversalAuth({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -295,6 +295,53 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "DELETE",
url: "/universal-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete Universal Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityUniversalAuth: IdentityUniversalAuthsSchema
})
}
},
handler: async (req) => {
const identityUniversalAuth = await server.services.identityUa.revokeIdentityUniversalAuth({
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: identityUniversalAuth.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId
}
}
});
return { identityUniversalAuth };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets", url: "/universal-auth/identities/:identityId/client-secrets",
@@ -325,14 +372,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { clientSecret, clientSecretData, orgId } = await server.services.identityUa.createUaClientSecret({ const { clientSecret, clientSecretData, orgId } =
actor: req.permission.type, await server.services.identityUa.createUniversalAuthClientSecret({
actorId: req.permission.id, actor: req.permission.type,
actorAuthMethod: req.permission.authMethod, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod,
identityId: req.params.identityId, actorOrgId: req.permission.orgId,
...req.body identityId: req.params.identityId,
}); ...req.body
});
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@@ -374,13 +422,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUaClientSecrets({ const { clientSecrets: clientSecretData, orgId } = await server.services.identityUa.getUniversalAuthClientSecrets(
actor: req.permission.type, {
actorId: req.permission.id, actor: req.permission.type,
actorAuthMethod: req.permission.authMethod, actorId: req.permission.id,
actorOrgId: req.permission.orgId, actorAuthMethod: req.permission.authMethod,
identityId: req.params.identityId actorOrgId: req.permission.orgId,
}); identityId: req.params.identityId
}
);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
@@ -396,6 +446,56 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Get Universal Auth Client Secret for identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.identityId),
clientSecretId: z.string().describe(UNIVERSAL_AUTH.GET_CLIENT_SECRET.clientSecretId)
}),
response: {
200: z.object({
clientSecretData: sanitizedClientSecretSchema
})
}
},
handler: async (req) => {
const clientSecretData = await server.services.identityUa.getUniversalAuthClientSecretById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
clientSecretId: req.params.clientSecretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecretData };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke", url: "/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
@@ -421,7 +521,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
const clientSecretData = await server.services.identityUa.revokeUaClientSecret({ const clientSecretData = await server.services.identityUa.revokeUniversalAuthClientSecret({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,

View File

@@ -9,7 +9,7 @@ import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router"; import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router"; import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
import { registerIdentityRouter } from "./identity-router"; import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua"; import { registerIdentityUaRouter } from "./identity-universal-auth-router";
import { registerIntegrationAuthRouter } from "./integration-auth-router"; import { registerIntegrationAuthRouter } from "./integration-auth-router";
import { registerIntegrationRouter } from "./integration-router"; import { registerIntegrationRouter } from "./integration-router";
import { registerInviteOrgRouter } from "./invite-org-router"; import { registerInviteOrgRouter } from "./invite-org-router";

View File

@@ -7,11 +7,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@@ -24,12 +25,13 @@ import {
TGetAwsAuthDTO, TGetAwsAuthDTO,
TGetCallerIdentityResponse, TGetCallerIdentityResponse,
TLoginAwsAuthDTO, TLoginAwsAuthDTO,
TRevokeAwsAuthDTO,
TUpdateAwsAuthDTO TUpdateAwsAuthDTO
} from "./identity-aws-auth-types"; } from "./identity-aws-auth-types";
type TIdentityAwsAuthServiceFactoryDep = { type TIdentityAwsAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -301,10 +303,54 @@ export const identityAwsAuthServiceFactory = ({
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId }; return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityAwsAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "The identity does not have aws auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke aws auth of identity with more privileged role"
});
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
const deletedAwsAuth = await identityAwsAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedAwsAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityAwsAuth;
};
return { return {
login, login,
attachAwsAuth, attachAwsAuth,
updateAwsAuth, updateAwsAuth,
getAwsAuth getAwsAuth,
revokeIdentityAwsAuth
}; };
}; };

View File

@@ -52,3 +52,7 @@ export type TGetCallerIdentityResponse = {
ResponseMetadata: { RequestId: string }; ResponseMetadata: { RequestId: string };
}; };
}; };
export type TRevokeAwsAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@@ -20,11 +21,15 @@ import {
TAttachAzureAuthDTO, TAttachAzureAuthDTO,
TGetAzureAuthDTO, TGetAzureAuthDTO,
TLoginAzureAuthDTO, TLoginAzureAuthDTO,
TRevokeAzureAuthDTO,
TUpdateAzureAuthDTO TUpdateAzureAuthDTO
} from "./identity-azure-auth-types"; } from "./identity-azure-auth-types";
type TIdentityAzureAuthServiceFactoryDep = { type TIdentityAzureAuthServiceFactoryDep = {
identityAzureAuthDAL: Pick<TIdentityAzureAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityAzureAuthDAL: Pick<
TIdentityAzureAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
@@ -277,10 +282,54 @@ export const identityAzureAuthServiceFactory = ({
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId }; return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityAzureAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "The identity does not have azure auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke azure auth of identity with more privileged role"
});
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
const deletedAzureAuth = await identityAzureAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedAzureAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityAzureAuth;
};
return { return {
login, login,
attachAzureAuth, attachAzureAuth,
updateAzureAuth, updateAzureAuth,
getAzureAuth getAzureAuth,
revokeIdentityAzureAuth
}; };
}; };

View File

@@ -118,3 +118,7 @@ export type TDecodedAzureAuthJwt = {
[key: string]: string; [key: string]: string;
}; };
}; };
export type TRevokeAzureAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -5,11 +5,12 @@ import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@@ -21,11 +22,12 @@ import {
TGcpIdentityDetails, TGcpIdentityDetails,
TGetGcpAuthDTO, TGetGcpAuthDTO,
TLoginGcpAuthDTO, TLoginGcpAuthDTO,
TRevokeGcpAuthDTO,
TUpdateGcpAuthDTO TUpdateGcpAuthDTO
} from "./identity-gcp-auth-types"; } from "./identity-gcp-auth-types";
type TIdentityGcpAuthServiceFactoryDep = { type TIdentityGcpAuthServiceFactoryDep = {
identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">; identityGcpAuthDAL: Pick<TIdentityGcpAuthDALFactory, "findOne" | "transaction" | "create" | "updateById" | "delete">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityDAL: Pick<TIdentityDALFactory, "updateById">; identityDAL: Pick<TIdentityDALFactory, "updateById">;
@@ -315,10 +317,54 @@ export const identityGcpAuthServiceFactory = ({
return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId }; return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityGcpAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
throw new BadRequestError({
message: "The identity does not have gcp auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke gcp auth of identity with more privileged role"
});
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
const deletedGcpAuth = await identityGcpAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedGcpAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityGcpAuth;
};
return { return {
login, login,
attachGcpAuth, attachGcpAuth,
updateGcpAuth, updateGcpAuth,
getGcpAuth getGcpAuth,
revokeIdentityGcpAuth
}; };
}; };

View File

@@ -76,3 +76,7 @@ export type TDecodedGcpIamAuthJwt = {
[key: string]: string; [key: string]: string;
}; };
}; };
export type TRevokeGcpAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -7,6 +7,7 @@ import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate }
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { import {
decryptSymmetric, decryptSymmetric,
@@ -16,11 +17,11 @@ import {
infisicalSymmetricDecrypt, infisicalSymmetricDecrypt,
infisicalSymmetricEncypt infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { AuthTokenType } from "../auth/auth-type"; import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal"; import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
@@ -32,13 +33,14 @@ import {
TCreateTokenReviewResponse, TCreateTokenReviewResponse,
TGetKubernetesAuthDTO, TGetKubernetesAuthDTO,
TLoginKubernetesAuthDTO, TLoginKubernetesAuthDTO,
TRevokeKubernetesAuthDTO,
TUpdateKubernetesAuthDTO TUpdateKubernetesAuthDTO
} from "./identity-kubernetes-auth-types"; } from "./identity-kubernetes-auth-types";
type TIdentityKubernetesAuthServiceFactoryDep = { type TIdentityKubernetesAuthServiceFactoryDep = {
identityKubernetesAuthDAL: Pick< identityKubernetesAuthDAL: Pick<
TIdentityKubernetesAuthDALFactory, TIdentityKubernetesAuthDALFactory,
"create" | "findOne" | "transaction" | "updateById" "create" | "findOne" | "transaction" | "updateById" | "delete"
>; >;
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">; identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">; identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne" | "findById">;
@@ -533,10 +535,54 @@ export const identityKubernetesAuthServiceFactory = ({
return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId }; return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId };
}; };
const revokeIdentityKubernetesAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
throw new BadRequestError({
message: "The identity does not have kubenetes auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke kubenetes auth of identity with more privileged role"
});
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
const deletedKubernetesAuth = await identityKubernetesAuthDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedKubernetesAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityKubernetesAuth;
};
return { return {
login, login,
attachKubernetesAuth, attachKubernetesAuth,
updateKubernetesAuth, updateKubernetesAuth,
getKubernetesAuth getKubernetesAuth,
revokeIdentityKubernetesAuth
}; };
}; };

View File

@@ -59,3 +59,7 @@ export type TCreateTokenReviewResponse = {
}; };
status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse; status: TCreateTokenReviewSuccessResponse | TCreateTokenReviewErrorResponse;
}; };
export type TRevokeKubernetesAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -25,7 +25,9 @@ import {
TCreateUaClientSecretDTO, TCreateUaClientSecretDTO,
TGetUaClientSecretsDTO, TGetUaClientSecretsDTO,
TGetUaDTO, TGetUaDTO,
TGetUniversalAuthClientSecretByIdDTO,
TRevokeUaClientSecretDTO, TRevokeUaClientSecretDTO,
TRevokeUaDTO,
TUpdateUaDTO TUpdateUaDTO
} from "./identity-ua-types"; } from "./identity-ua-types";
@@ -136,7 +138,7 @@ export const identityUaServiceFactory = ({
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg }; return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
}; };
const attachUa = async ({ const attachUniversalAuth = async ({
accessTokenMaxTTL, accessTokenMaxTTL,
identityId, identityId,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
@@ -227,7 +229,7 @@ export const identityUaServiceFactory = ({
return { ...identityUa, orgId: identityMembershipOrg.orgId }; return { ...identityUa, orgId: identityMembershipOrg.orgId };
}; };
const updateUa = async ({ const updateUniversalAuth = async ({
accessTokenMaxTTL, accessTokenMaxTTL,
identityId, identityId,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
@@ -312,7 +314,7 @@ export const identityUaServiceFactory = ({
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId }; return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
}; };
const getIdentityUa = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => { const getIdentityUniversalAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" }); if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral) if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
@@ -333,7 +335,50 @@ export const identityUaServiceFactory = ({
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId }; return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
}; };
const createUaClientSecret = async ({ const revokeIdentityUniversalAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to revoke universal auth of identity with more privileged role"
});
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
const deletedUniversalAuth = await identityUaDAL.delete({ identityId }, tx);
await identityDAL.updateById(identityId, { authMethod: null }, tx);
return { ...deletedUniversalAuth?.[0], orgId: identityMembershipOrg.orgId };
});
return revokedIdentityUniversalAuth;
};
const createUniversalAuthClientSecret = async ({
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@@ -396,7 +441,7 @@ export const identityUaServiceFactory = ({
}; };
}; };
const getUaClientSecrets = async ({ const getUniversalAuthClientSecrets = async ({
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@@ -442,7 +487,47 @@ export const identityUaServiceFactory = ({
return { clientSecrets, orgId: identityMembershipOrg.orgId }; return { clientSecrets, orgId: identityMembershipOrg.orgId };
}; };
const revokeUaClientSecret = async ({ const getUniversalAuthClientSecretById = async ({
identityId,
actorId,
actor,
actorOrgId,
actorAuthMethod,
clientSecretId
}: TGetUniversalAuthClientSecretByIdDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge)
throw new ForbiddenRequestError({
message: "Failed to read identity client secret of project with more privileged role"
});
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
};
const revokeUniversalAuthClientSecret = async ({
identityId, identityId,
actorId, actorId,
actor, actor,
@@ -475,7 +560,7 @@ export const identityUaServiceFactory = ({
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
if (!hasPriviledge) if (!hasPriviledge)
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
message: "Failed to add identity to project with more privileged role" message: "Failed to revoke identity client secret with more privileged role"
}); });
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
@@ -486,11 +571,13 @@ export const identityUaServiceFactory = ({
return { return {
login, login,
attachUa, attachUniversalAuth,
updateUa, updateUniversalAuth,
getIdentityUa, getIdentityUniversalAuth,
createUaClientSecret, revokeIdentityUniversalAuth,
getUaClientSecrets, createUniversalAuthClientSecret,
revokeUaClientSecret getUniversalAuthClientSecrets,
revokeUniversalAuthClientSecret,
getUniversalAuthClientSecretById
}; };
}; };

View File

@@ -22,6 +22,10 @@ export type TGetUaDTO = {
identityId: string; identityId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TRevokeUaDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TCreateUaClientSecretDTO = { export type TCreateUaClientSecretDTO = {
identityId: string; identityId: string;
description: string; description: string;
@@ -37,3 +41,8 @@ export type TRevokeUaClientSecretDTO = {
identityId: string; identityId: string;
clientSecretId: string; clientSecretId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetUniversalAuthClientSecretByIdDTO = {
identityId: string;
clientSecretId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -27,10 +27,10 @@ export const identityOrgDALFactory = (db: TDbClient) => {
} }
}; };
const findByOrgId = async (orgId: string, tx?: Knex) => { const find = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
try { try {
const docs = await (tx || db)(TableName.IdentityOrgMembership) const docs = await (tx || db)(TableName.IdentityOrgMembership)
.where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .where(filter)
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`) .join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.select(selectAllTableCols(TableName.IdentityOrgMembership)) .select(selectAllTableCols(TableName.IdentityOrgMembership))
@@ -79,5 +79,5 @@ export const identityOrgDALFactory = (db: TDbClient) => {
} }
}; };
return { ...identityOrgOrm, findOne, findByOrgId }; return { ...identityOrgOrm, find, findOne };
}; };

View File

@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas"; import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { isAtLeastAsPrivileged } from "@app/lib/casl";
@@ -10,7 +10,7 @@ import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TIdentityDALFactory } from "./identity-dal"; import { TIdentityDALFactory } from "./identity-dal";
import { TIdentityOrgDALFactory } from "./identity-org-dal"; import { TIdentityOrgDALFactory } from "./identity-org-dal";
import { TCreateIdentityDTO, TDeleteIdentityDTO, TUpdateIdentityDTO } from "./identity-types"; import { TCreateIdentityDTO, TDeleteIdentityDTO, TGetIdentityByIdDTO, TUpdateIdentityDTO } from "./identity-types";
type TIdentityServiceFactoryDep = { type TIdentityServiceFactoryDep = {
identityDAL: TIdentityDALFactory; identityDAL: TIdentityDALFactory;
@@ -126,6 +126,24 @@ export const identityServiceFactory = ({
return { ...identity, orgId: identityOrgMembership.orgId }; return { ...identity, orgId: identityOrgMembership.orgId };
}; };
const getIdentityById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetIdentityByIdDTO) => {
const doc = await identityOrgMembershipDAL.find({
[`${TableName.IdentityOrgMembership}.identityId` as "identityId"]: id
});
const identity = doc[0];
if (!identity) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identity.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return identity;
};
const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => { const deleteIdentity = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TDeleteIdentityDTO) => {
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id }); const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` }); if (!identityOrgMembership) throw new BadRequestError({ message: `Failed to find identity with id ${id}` });
@@ -157,7 +175,9 @@ export const identityServiceFactory = ({
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityOrgMembershipDAL.findByOrgId(orgId); const identityMemberships = await identityOrgMembershipDAL.find({
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId
});
return identityMemberships; return identityMemberships;
}; };
@@ -165,6 +185,7 @@ export const identityServiceFactory = ({
createIdentity, createIdentity,
updateIdentity, updateIdentity,
deleteIdentity, deleteIdentity,
listOrgIdentities listOrgIdentities,
getIdentityById
}; };
}; };

View File

@@ -16,6 +16,10 @@ export type TDeleteIdentityDTO = {
id: string; id: string;
} & Omit<TOrgPermission, "orgId">; } & Omit<TOrgPermission, "orgId">;
export type TGetIdentityByIdDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export interface TIdentityTrustedIp { export interface TIdentityTrustedIp {
ipAddress: string; ipAddress: string;
type: IPType; type: IPType;

View File

@@ -0,0 +1,5 @@
---
title: "Get By ID"
openapi: "GET /api/v1/identities/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/identities"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get Client Secret By ID"
openapi: "GET /api/v1/auth/universal-auth/identities/{identityId}/client-secrets/{clientSecretId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Revoke"
openapi: "DELETE /api/v1/auth/universal-auth/identities/{identityId}"
---

View File

@@ -419,7 +419,9 @@
"pages": [ "pages": [
"api-reference/endpoints/identities/create", "api-reference/endpoints/identities/create",
"api-reference/endpoints/identities/update", "api-reference/endpoints/identities/update",
"api-reference/endpoints/identities/delete" "api-reference/endpoints/identities/delete",
"api-reference/endpoints/identities/get-by-id",
"api-reference/endpoints/identities/list"
] ]
}, },
{ {
@@ -429,9 +431,11 @@
"api-reference/endpoints/universal-auth/attach", "api-reference/endpoints/universal-auth/attach",
"api-reference/endpoints/universal-auth/retrieve", "api-reference/endpoints/universal-auth/retrieve",
"api-reference/endpoints/universal-auth/update", "api-reference/endpoints/universal-auth/update",
"api-reference/endpoints/universal-auth/revoke",
"api-reference/endpoints/universal-auth/create-client-secret", "api-reference/endpoints/universal-auth/create-client-secret",
"api-reference/endpoints/universal-auth/list-client-secrets", "api-reference/endpoints/universal-auth/list-client-secrets",
"api-reference/endpoints/universal-auth/revoke-client-secret", "api-reference/endpoints/universal-auth/revoke-client-secret",
"api-reference/endpoints/universal-auth/get-client-secret-by-id",
"api-reference/endpoints/universal-auth/renew-access-token", "api-reference/endpoints/universal-auth/renew-access-token",
"api-reference/endpoints/universal-auth/revoke-access-token" "api-reference/endpoints/universal-auth/revoke-access-token"
] ]