feat: Add AWS CDK project and Helm charts for Beckn-Onix deployment on AWS cloud
This commit is contained in:
67
aws-cdk/beckn-cdk/lib/config.ts
Normal file
67
aws-cdk/beckn-cdk/lib/config.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import path = require("path");
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, "../.env") });
|
||||
|
||||
export type ConfigProps = {
|
||||
REGION: string,
|
||||
ACCOUNT: string,
|
||||
REPOSITORY: string,
|
||||
REGISTRY_RELEASE_NAME: string;
|
||||
GATEWAY_RELEASE_NAME: string;
|
||||
BAP_RELEASE_NAME: string;
|
||||
BPP_RELEASE_NAME: string,
|
||||
RDS_USER: string,
|
||||
CERT_ARN: string,
|
||||
REGISTRY_URL: string,
|
||||
MAX_AZS: number,
|
||||
EKS_CLUSTER_NAME: string,
|
||||
CIDR: string,
|
||||
EC2_NODES_COUNT: number;
|
||||
EC2_INSTANCE_TYPE: string;
|
||||
ROLE_ARN: string;
|
||||
DOCDB_PASSWORD: string;
|
||||
RABBITMQ_PASSWORD: string;
|
||||
NAMESPACE: string;
|
||||
BAP_PUBLIC_KEY: string;
|
||||
BAP_PRIVATE_KEY: string;
|
||||
BPP_PUBLIC_KEY: string;
|
||||
BPP_PRIVATE_KEY: string;
|
||||
REGISTRY_EXTERNAL_DOMAIN: string,
|
||||
GATEWAY_EXTERNAL_DOMAIN: string;
|
||||
BAP_EXTERNAL_DOMAIN: string;
|
||||
BPP_EXTERNAL_DOMAIN: string;
|
||||
|
||||
};
|
||||
|
||||
export const getConfig = (): ConfigProps => ({
|
||||
REGION: process.env.REGION || "ap-south-1",
|
||||
ACCOUNT: process.env.ACCOUNT || "",
|
||||
REPOSITORY: process.env.BECKN_ONIX_HELM_REPOSITORY || "",
|
||||
MAX_AZS: Number(process.env.MAZ_AZs) || 2,
|
||||
REGISTRY_RELEASE_NAME: "beckn-onix-registry",
|
||||
GATEWAY_RELEASE_NAME: "beckn-onix-gateway",
|
||||
BAP_RELEASE_NAME: "beckn-onix-bap",
|
||||
BPP_RELEASE_NAME: "beckn-onix-bpp",
|
||||
RDS_USER: process.env.RDS_USER || "postgres",
|
||||
CERT_ARN: process.env.CERT_ARN || "", // user must provide it
|
||||
REGISTRY_URL: process.env.REGISTRY_URL || "", // beckn-onix reg url
|
||||
EKS_CLUSTER_NAME: process.env.EKS_CLUSTER_NAME || "beckn-onix",
|
||||
CIDR: process.env.CIDR || "10.20.0.0/16",
|
||||
EC2_NODES_COUNT: Number(process.env.EC2_NODES_COUNT) || 2,
|
||||
EC2_INSTANCE_TYPE: process.env.EC2_INSTANCE_TYPE || "t3.large",
|
||||
ROLE_ARN: process.env.ROLE_ARN || "",
|
||||
DOCDB_PASSWORD: process.env.DOCDB_PASSWORD || "",
|
||||
RABBITMQ_PASSWORD: process.env.RABBITMQ_PASSWORD || "",
|
||||
NAMESPACE: "-common-services",
|
||||
BAP_PUBLIC_KEY: process.env.BAP_PUBLIC_KEY || "",
|
||||
BAP_PRIVATE_KEY: process.env.BAP_PRIVATE_KEY || "",
|
||||
BPP_PUBLIC_KEY: process.env.BPP_PUBLIC_KEY || "",
|
||||
BPP_PRIVATE_KEY: process.env.BPP_PRIVATE_KEY || "",
|
||||
REGISTRY_EXTERNAL_DOMAIN: process.env.REGISTRY_EXTERNAL_DOMAIN || "", // user must provide it
|
||||
GATEWAY_EXTERNAL_DOMAIN: process.env.GATEWAY_EXTERNAL_DOMAIN || "", // user must provide it
|
||||
BAP_EXTERNAL_DOMAIN: process.env.BAP_EXTERNAL_DOMAIN || "", // user must provide it
|
||||
BPP_EXTERNAL_DOMAIN: process.env.BPP_EXTERNAL_DOMAIN || "", // user must provide it
|
||||
|
||||
|
||||
});
|
||||
64
aws-cdk/beckn-cdk/lib/documentdb-stack.ts
Normal file
64
aws-cdk/beckn-cdk/lib/documentdb-stack.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as docdb from 'aws-cdk-lib/aws-docdb';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
interface DocumentDbStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class DocumentDbStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props: DocumentDbStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
// Use environment variable from .env file or fallback to a default value
|
||||
const docDbPassword = new cdk.CfnParameter(this, 'DocDbPassword', {
|
||||
type: 'String',
|
||||
description: 'The password for the DocumentDB cluster admin user',
|
||||
noEcho: true,
|
||||
default: props.config.DOCDB_PASSWORD || '', // Use environment variable
|
||||
});
|
||||
|
||||
// Security group for DocumentDB
|
||||
const docDbSecurityGroup = new ec2.SecurityGroup(this, 'DocDbSecurityGroup', {
|
||||
vpc: props.vpc,
|
||||
description: 'Security group for DocumentDB',
|
||||
allowAllOutbound: true,
|
||||
});
|
||||
|
||||
docDbSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.vpc.vpcCidrBlock), ec2.Port.tcp(27017), 'Allow DocumentDB traffic on port 27017');
|
||||
|
||||
// DocumentDB subnet group
|
||||
const docDbSubnetGroup = new docdb.CfnDBSubnetGroup(this, 'DocDbSubnetGroup', {
|
||||
dbSubnetGroupDescription: 'Subnet group for DocumentDB',
|
||||
subnetIds: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }).subnetIds,
|
||||
});
|
||||
|
||||
// DocumentDB cluster
|
||||
const docDbCluster = new docdb.CfnDBCluster(this, 'DocDbCluster', {
|
||||
masterUsername: 'beckn',
|
||||
masterUserPassword: docDbPassword.valueAsString, // Password entered by the user
|
||||
dbClusterIdentifier: 'MyDocDbCluster',
|
||||
engineVersion: '4.0.0',
|
||||
vpcSecurityGroupIds: [docDbSecurityGroup.securityGroupId],
|
||||
dbSubnetGroupName: docDbSubnetGroup.ref,
|
||||
});
|
||||
|
||||
// Create 2 DocumentDB instances
|
||||
new docdb.CfnDBInstance(this, 'DocDbInstance1', {
|
||||
dbClusterIdentifier: docDbCluster.ref,
|
||||
dbInstanceClass: 'db.r5.large',
|
||||
});
|
||||
|
||||
new docdb.CfnDBInstance(this, 'DocDbInstance2', {
|
||||
dbClusterIdentifier: docDbCluster.ref,
|
||||
dbInstanceClass: 'db.r5.large',
|
||||
});
|
||||
}
|
||||
}
|
||||
149
aws-cdk/beckn-cdk/lib/eks-stack.ts
Normal file
149
aws-cdk/beckn-cdk/lib/eks-stack.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { KubectlV30Layer } from '@aws-cdk/lambda-layer-kubectl-v30';
|
||||
// import { CfnAutoScalingGroup } from 'aws-cdk-lib/aws-autoscaling';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
export interface EksStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class EksStack extends cdk.Stack {
|
||||
public readonly cluster: eks.Cluster;
|
||||
public readonly eksSecGrp: ec2.SecurityGroup;
|
||||
|
||||
constructor(scope: Construct, id: string, props: EksStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const config = props.config;
|
||||
|
||||
|
||||
const vpc = props.vpc;
|
||||
const cidr = config.CIDR; // from config file
|
||||
const EKS_CLUSTER_NAME = config.EKS_CLUSTER_NAME; // take it from config file
|
||||
// const ROLE_ARN = 'ROLE_ARN'; // take form config file
|
||||
const ROLE_ARN = config.ROLE_ARN;
|
||||
|
||||
const securityGroupEKS = new ec2.SecurityGroup(this, "EKSSecurityGroup", {
|
||||
vpc: vpc,
|
||||
allowAllOutbound: true,
|
||||
description: "Security group for EKS",
|
||||
});
|
||||
|
||||
securityGroupEKS.addIngressRule(
|
||||
ec2.Peer.ipv4(cidr),
|
||||
ec2.Port.allTraffic(),
|
||||
"Allow EKS traffic"
|
||||
|
||||
);
|
||||
// securityGroupEKS.addIngressRule(
|
||||
// ec2.Peer.securityGroupId(securityGroupEKS.securityGroupId),
|
||||
// ec2.Port.allTraffic(),
|
||||
// "Allow EKS traffic"
|
||||
// );
|
||||
|
||||
const iamRole = iam.Role.fromRoleArn(this, "MyIAMRole", ROLE_ARN);
|
||||
|
||||
// Create the EKS cluster
|
||||
this.cluster = new eks.Cluster(this, 'EksCluster', {
|
||||
vpc: vpc,
|
||||
vpcSubnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }],
|
||||
defaultCapacity: 0,
|
||||
// defaultCapacityInstance: new ec2.InstanceType(config.EC2_INSTANCE_TYPE),
|
||||
kubectlLayer: new KubectlV30Layer(this, 'KubectlLayer'),
|
||||
version: eks.KubernetesVersion.V1_30,
|
||||
securityGroup: securityGroupEKS,
|
||||
endpointAccess: eks.EndpointAccess.PUBLIC_AND_PRIVATE,
|
||||
ipFamily: eks.IpFamily.IP_V4,
|
||||
clusterName: EKS_CLUSTER_NAME,
|
||||
mastersRole: iamRole, // Assign the admin role to the cluster
|
||||
outputClusterName: true,
|
||||
outputConfigCommand: true,
|
||||
authenticationMode: eks.AuthenticationMode.API_AND_CONFIG_MAP,
|
||||
bootstrapClusterCreatorAdminPermissions: true,
|
||||
|
||||
albController: {
|
||||
version: eks.AlbControllerVersion.V2_8_1,
|
||||
repository: "public.ecr.aws/eks/aws-load-balancer-controller",
|
||||
},
|
||||
});
|
||||
|
||||
const key1 = this.cluster.openIdConnectProvider.openIdConnectProviderIssuer;
|
||||
const stringEquals = new cdk.CfnJson(this, 'ConditionJson', {
|
||||
value: {
|
||||
[`${key1}:sub`]: ['system:serviceaccount:kube-system:ebs-csi-controller-sa', 'system:serviceaccount:kube-system:efs-csi-controller-sa'],
|
||||
[`${key1}:aud`]: 'sts.amazonaws.com'
|
||||
},
|
||||
})
|
||||
|
||||
const oidcEKSCSIRole = new iam.Role(this, "OIDCRole", {
|
||||
assumedBy: new iam.FederatedPrincipal(
|
||||
`arn:aws:iam::${this.account}:oidc-provider/${this.cluster.clusterOpenIdConnectIssuer}`,
|
||||
{
|
||||
StringEquals: stringEquals,
|
||||
|
||||
},
|
||||
"sts:AssumeRoleWithWebIdentity"
|
||||
),
|
||||
});
|
||||
|
||||
// Attach a managed policy to the role
|
||||
oidcEKSCSIRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonEBSCSIDriverPolicy"))
|
||||
oidcEKSCSIRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonEFSCSIDriverPolicy"))
|
||||
|
||||
const ebscsi = new eks.CfnAddon(this, "addonEbsCsi",
|
||||
{
|
||||
addonName: "aws-ebs-csi-driver",
|
||||
clusterName: this.cluster.clusterName,
|
||||
serviceAccountRoleArn: oidcEKSCSIRole.roleArn
|
||||
}
|
||||
);
|
||||
|
||||
const efscsi = new eks.CfnAddon(this, "addonEfsCsi",
|
||||
{
|
||||
addonName: "aws-efs-csi-driver",
|
||||
clusterName: this.cluster.clusterName,
|
||||
serviceAccountRoleArn: oidcEKSCSIRole.roleArn
|
||||
}
|
||||
);
|
||||
|
||||
new cdk.CfnOutput(this, String("OIDC-issuer"), {
|
||||
value: this.cluster.clusterOpenIdConnectIssuer,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, String("OIDC-issuerURL"), {
|
||||
value: this.cluster.clusterOpenIdConnectIssuerUrl,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "EKS Cluster Name", {
|
||||
value: this.cluster.clusterName,
|
||||
});
|
||||
new cdk.CfnOutput(this, "EKS Cluster Arn", {
|
||||
value: this.cluster.clusterArn,
|
||||
});
|
||||
|
||||
const launchTemplate = new ec2.CfnLaunchTemplate(this, 'MyLaunchTemplate', {
|
||||
launchTemplateData: {
|
||||
instanceType: config.EC2_INSTANCE_TYPE,
|
||||
securityGroupIds: [this.cluster.clusterSecurityGroupId, securityGroupEKS.securityGroupId],
|
||||
}
|
||||
});
|
||||
|
||||
// Create node group using the launch template
|
||||
this.cluster.addNodegroupCapacity('CustomNodeGroup', {
|
||||
amiType: eks.NodegroupAmiType.AL2_X86_64,
|
||||
desiredSize: config.EC2_NODES_COUNT,
|
||||
launchTemplateSpec: {
|
||||
id: launchTemplate.ref,
|
||||
version: launchTemplate.attrLatestVersionNumber,
|
||||
},
|
||||
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
|
||||
});
|
||||
|
||||
this.eksSecGrp = securityGroupEKS;
|
||||
}
|
||||
}
|
||||
113
aws-cdk/beckn-cdk/lib/helm-bap.ts
Normal file
113
aws-cdk/beckn-cdk/lib/helm-bap.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as helm from 'aws-cdk-lib/aws-eks';
|
||||
import { Stack, StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
import * as efs from 'aws-cdk-lib/aws-efs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
|
||||
|
||||
interface HelmBapStackProps extends StackProps {
|
||||
config: ConfigProps;
|
||||
eksCluster: eks.Cluster;
|
||||
isSandbox: boolean;
|
||||
eksSecGrp: ec2.SecurityGroup;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class HelmBapStack extends Stack {
|
||||
constructor(scope: Construct, id: string, props: HelmBapStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const eksCluster = props.eksCluster;
|
||||
const externalDomain = props.config.BAP_EXTERNAL_DOMAIN;
|
||||
const certArn = props.config.CERT_ARN;
|
||||
const releaseName = props.config.BAP_RELEASE_NAME;
|
||||
const repository = props.config.REPOSITORY;
|
||||
const registryUrl = props.config.REGISTRY_URL;
|
||||
const bapPrivateKey = props.config.BAP_PRIVATE_KEY;
|
||||
const bapPublicKey = props.config.BAP_PUBLIC_KEY;
|
||||
|
||||
const isSandbox = props.isSandbox;
|
||||
|
||||
const myFileSystemPolicy = new iam.PolicyDocument({
|
||||
statements: [new iam.PolicyStatement({
|
||||
actions: [
|
||||
'elasticfilesystem:ClientRootAccess',
|
||||
'elasticfilesystem:ClientWrite',
|
||||
'elasticfilesystem:ClientMount',
|
||||
],
|
||||
principals: [new iam.ArnPrincipal('*')],
|
||||
resources: ['*'],
|
||||
conditions: {
|
||||
Bool: {
|
||||
'elasticfilesystem:AccessedViaMountTarget': 'true',
|
||||
},
|
||||
},
|
||||
})],
|
||||
});
|
||||
|
||||
const efsBapFileSystemId = new efs.FileSystem(this, 'Beckn-Onix-Bap', {
|
||||
vpc: props.vpc,
|
||||
securityGroup: props.eksSecGrp,
|
||||
fileSystemPolicy: myFileSystemPolicy,
|
||||
});
|
||||
|
||||
// let efsBapFileSystemId: string | undefined;
|
||||
// const existingFileSystemId = cdk.Fn.importValue('EfsBapFileSystemId');
|
||||
|
||||
// if(existingFileSystemId){
|
||||
// efsBapFileSystemId = existingFileSystemId;
|
||||
// } else{
|
||||
// const efsBapFileSystem = new efs.FileSystem(this, 'Beckn-Onix-Bap', {
|
||||
// vpc: props.vpc,
|
||||
// securityGroup: props.eksSecGrp,
|
||||
// });
|
||||
|
||||
// efsBapFileSystemId = efsBapFileSystem.fileSystemId;
|
||||
|
||||
// new cdk.CfnOutput(this, 'EfsBapFileSystemId', {
|
||||
// value: efsBapFileSystemId,
|
||||
// exportName: 'EfsBapFileSystemId',
|
||||
// })
|
||||
// }
|
||||
|
||||
// const efsBapFileSystemId = new efs.FileSystem(this, 'Beckn-Onix-Bap', {
|
||||
// vpc: props.vpc,
|
||||
// });
|
||||
|
||||
new helm.HelmChart(this, 'baphelm', {
|
||||
cluster: eksCluster,
|
||||
chart: 'beckn-onix-bap',
|
||||
release: releaseName,
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
global: {
|
||||
isSandbox: isSandbox,
|
||||
externalDomain: externalDomain,
|
||||
registry_url: registryUrl,
|
||||
bap: {
|
||||
privateKey: bapPrivateKey,
|
||||
publicKey: bapPublicKey,
|
||||
},
|
||||
efs: {
|
||||
fileSystemId: efsBapFileSystemId.fileSystemId,
|
||||
},
|
||||
ingress: {
|
||||
tls: {
|
||||
certificateArn: certArn,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
new cdk.CfnOutput(this, String("EksFileSystemId"), {
|
||||
value: efsBapFileSystemId.fileSystemId,
|
||||
});
|
||||
}
|
||||
}
|
||||
90
aws-cdk/beckn-cdk/lib/helm-beckn-common-services.ts
Normal file
90
aws-cdk/beckn-cdk/lib/helm-beckn-common-services.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as helm from 'aws-cdk-lib/aws-eks';
|
||||
import { Stack, StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
|
||||
interface HelmCommonServicesStackProps extends StackProps {
|
||||
config: ConfigProps;
|
||||
eksCluster: eks.Cluster;
|
||||
service: string,
|
||||
}
|
||||
|
||||
export class HelmCommonServicesStack extends Stack {
|
||||
constructor(scope: Construct, id: string, props: HelmCommonServicesStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const eksCluster = props.eksCluster;
|
||||
const service = props.service;
|
||||
const repository = "https://charts.bitnami.com/bitnami";
|
||||
const namespace = props.config.NAMESPACE;
|
||||
|
||||
const generateRandomPassword = (length: number) => {
|
||||
return crypto.randomBytes(length).toString('hex').slice(0, length);
|
||||
};
|
||||
const rabbitMQPassword = generateRandomPassword(12);
|
||||
|
||||
new helm.HelmChart(this, "RedisHelmChart", {
|
||||
cluster: eksCluster,
|
||||
chart: "redis",
|
||||
namespace: service + namespace,
|
||||
release: "redis",
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
auth: {
|
||||
enabled: false
|
||||
},
|
||||
replica: {
|
||||
replicaCount: 0
|
||||
},
|
||||
master: {
|
||||
persistence: {
|
||||
storageClass: "gp2"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new helm.HelmChart(this, "MongoDBHelmChart", {
|
||||
cluster: eksCluster,
|
||||
chart: "mongodb",
|
||||
namespace: service + namespace,
|
||||
release: "mongodb",
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
persistence: {
|
||||
storageClass: "gp2"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new helm.HelmChart(this, "RabbitMQHelmChart", {
|
||||
cluster: eksCluster,
|
||||
chart: "rabbitmq",
|
||||
namespace: service + namespace,
|
||||
release: "rabbitmq",
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
persistence: {
|
||||
enabled: true,
|
||||
storageClass: "gp2"
|
||||
},
|
||||
auth: {
|
||||
username: "beckn",
|
||||
password: "beckn1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// new cdk.CfnOutput(this, String("RabbimqPassword"), {
|
||||
// value: rabbitMQPassword,
|
||||
// });
|
||||
|
||||
}
|
||||
}
|
||||
89
aws-cdk/beckn-cdk/lib/helm-bpp.ts
Normal file
89
aws-cdk/beckn-cdk/lib/helm-bpp.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as helm from 'aws-cdk-lib/aws-eks';
|
||||
import { Stack, StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as efs from 'aws-cdk-lib/aws-efs';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
|
||||
interface HelmBppStackProps extends StackProps {
|
||||
config: ConfigProps;
|
||||
vpc: ec2.Vpc;
|
||||
isSandbox: boolean;
|
||||
eksSecGrp: ec2.SecurityGroup;
|
||||
eksCluster: eks.Cluster;
|
||||
}
|
||||
|
||||
export class HelmBppStack extends Stack {
|
||||
constructor(scope: Construct, id: string, props: HelmBppStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const eksCluster = props.eksCluster;
|
||||
const externalDomain = props.config.BPP_EXTERNAL_DOMAIN;
|
||||
const certArn = props.config.CERT_ARN;
|
||||
const releaseName = props.config.BPP_RELEASE_NAME;
|
||||
const repository = props.config.REPOSITORY;
|
||||
const registryUrl = props.config.REGISTRY_URL;
|
||||
|
||||
const bppPrivateKey = props.config.BPP_PRIVATE_KEY;
|
||||
const bppPublicKey = props.config.BPP_PUBLIC_KEY;
|
||||
|
||||
const isSandbox = props.isSandbox;
|
||||
|
||||
const myFileSystemPolicy = new iam.PolicyDocument({
|
||||
statements: [new iam.PolicyStatement({
|
||||
actions: [
|
||||
'elasticfilesystem:ClientRootAccess',
|
||||
'elasticfilesystem:ClientWrite',
|
||||
'elasticfilesystem:ClientMount',
|
||||
],
|
||||
principals: [new iam.ArnPrincipal('*')],
|
||||
resources: ['*'],
|
||||
conditions: {
|
||||
Bool: {
|
||||
'elasticfilesystem:AccessedViaMountTarget': 'true',
|
||||
},
|
||||
},
|
||||
})],
|
||||
});
|
||||
|
||||
const efsBppFileSystemId = new efs.FileSystem(this, 'Beckn-Onix-Bpp', {
|
||||
vpc: props.vpc,
|
||||
securityGroup: props.eksSecGrp,
|
||||
fileSystemPolicy: myFileSystemPolicy,
|
||||
});
|
||||
|
||||
new helm.HelmChart(this, 'Bpphelm', {
|
||||
cluster: eksCluster,
|
||||
chart: 'beckn-onix-bpp',
|
||||
release: releaseName,
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
global: {
|
||||
isSandbox: isSandbox,
|
||||
externalDomain: externalDomain,
|
||||
registry_url: registryUrl,
|
||||
bpp: {
|
||||
privateKey: bppPrivateKey,
|
||||
publicKey: bppPublicKey,
|
||||
},
|
||||
efs: {
|
||||
fileSystemId: efsBppFileSystemId.fileSystemId,
|
||||
},
|
||||
ingress: {
|
||||
tls: {
|
||||
certificateArn: certArn,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
new cdk.CfnOutput(this, String("EksFileSystemId"), {
|
||||
value: efsBppFileSystemId.fileSystemId,
|
||||
});
|
||||
}
|
||||
}
|
||||
54
aws-cdk/beckn-cdk/lib/helm-gateway.ts
Normal file
54
aws-cdk/beckn-cdk/lib/helm-gateway.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as helm from 'aws-cdk-lib/aws-eks';
|
||||
import { Stack, StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
interface HelmGAtewayStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
eksCluster: eks.Cluster;
|
||||
rdsHost: string;
|
||||
rdsPassword: string;
|
||||
}
|
||||
|
||||
export class HelmGatewayStack extends Stack {
|
||||
constructor(scope: Construct, id: string, props: HelmGAtewayStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const eksCluster = props.eksCluster;
|
||||
const externalDomain = props.config.GATEWAY_EXTERNAL_DOMAIN;
|
||||
const certArn = props.config.CERT_ARN;
|
||||
const registryUrl = props.config.REGISTRY_URL;
|
||||
|
||||
const releaseName = props.config.GATEWAY_RELEASE_NAME;
|
||||
const repository = props.config.REPOSITORY;
|
||||
|
||||
const rdsHost = props.rdsHost;
|
||||
const rdsPassword = props.rdsPassword;
|
||||
|
||||
new helm.HelmChart(this, "gatewayhelm", {
|
||||
cluster: eksCluster,
|
||||
chart: "beckn-onix-gateway",
|
||||
release: releaseName,
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
externalDomain: externalDomain,
|
||||
registry_url: registryUrl,
|
||||
database: {
|
||||
host: rdsHost,
|
||||
password: rdsPassword,
|
||||
},
|
||||
ingress: {
|
||||
tls:
|
||||
{
|
||||
certificateArn: certArn,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
50
aws-cdk/beckn-cdk/lib/helm-registry.ts
Normal file
50
aws-cdk/beckn-cdk/lib/helm-registry.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as eks from 'aws-cdk-lib/aws-eks';
|
||||
import * as helm from 'aws-cdk-lib/aws-eks';
|
||||
import { Stack, StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
interface HelmRegistryStackProps extends StackProps {
|
||||
config: ConfigProps;
|
||||
eksCluster: eks.Cluster;
|
||||
rdsHost: string;
|
||||
rdsPassword: string;
|
||||
}
|
||||
|
||||
export class HelmRegistryStack extends Stack {
|
||||
constructor(scope: Construct, id: string, props: HelmRegistryStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const eksCluster = props.eksCluster;
|
||||
const externalDomain = props.config.REGISTRY_EXTERNAL_DOMAIN;
|
||||
const certArn = props.config.CERT_ARN;
|
||||
const releaseName = props.config.REGISTRY_RELEASE_NAME;
|
||||
const repository = props.config.REPOSITORY;
|
||||
|
||||
const rdsHost = props.rdsHost;
|
||||
const rdsPassword = props.rdsPassword;
|
||||
|
||||
new helm.HelmChart(this, "registryhelm", {
|
||||
cluster: eksCluster,
|
||||
chart: "beckn-onix-registry",
|
||||
release: releaseName,
|
||||
wait: false,
|
||||
repository: repository,
|
||||
values: {
|
||||
externalDomain: externalDomain,
|
||||
database: {
|
||||
host: rdsHost,
|
||||
password: rdsPassword
|
||||
},
|
||||
ingress: {
|
||||
tls:
|
||||
{
|
||||
certificateArn: certArn,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
66
aws-cdk/beckn-cdk/lib/rabbitmq-stack.ts
Normal file
66
aws-cdk/beckn-cdk/lib/rabbitmq-stack.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as amazonmq from 'aws-cdk-lib/aws-amazonmq';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
interface RabbitMqStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class RabbitMqStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props: RabbitMqStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
// Prompt for the RabbitMQ admin password using environment variable
|
||||
const rabbitMqPassword = new cdk.CfnParameter(this, 'RabbitMqPassword', {
|
||||
type: 'String',
|
||||
description: 'The password for the RabbitMQ broker admin user',
|
||||
noEcho: true, // Ensure the password is hidden from the console
|
||||
default: props.config.RABBITMQ_PASSWORD || '', // Use the password from .env or set a fallback
|
||||
});
|
||||
|
||||
// Security group for RabbitMQ
|
||||
const rabbitMqSecurityGroup = new ec2.SecurityGroup(this, 'RabbitMqSecurityGroup', {
|
||||
vpc: props.vpc,
|
||||
description: 'Security group for RabbitMQ broker',
|
||||
allowAllOutbound: true,
|
||||
});
|
||||
|
||||
rabbitMqSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.vpc.vpcCidrBlock), ec2.Port.tcp(5672), 'Allow RabbitMQ traffic on port 5672');
|
||||
rabbitMqSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.vpc.vpcCidrBlock), ec2.Port.tcp(15672), 'Allow RabbitMQ management traffic');
|
||||
|
||||
// Select a single private subnet for the RabbitMQ Broker
|
||||
const privateSubnets = props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }).subnets;
|
||||
|
||||
// Ensure there's at least one subnet, and use the first one
|
||||
if (privateSubnets.length === 0) {
|
||||
throw new Error('No private subnets found in the VPC');
|
||||
}
|
||||
|
||||
const selectedSubnet = privateSubnets[0]; // Use the first subnet
|
||||
|
||||
// RabbitMQ Broker
|
||||
new amazonmq.CfnBroker(this, 'RabbitMqBroker', {
|
||||
brokerName: 'MyRabbitMqBroker',
|
||||
engineType: 'RABBITMQ',
|
||||
engineVersion: '3.10.25',
|
||||
deploymentMode: 'SINGLE_INSTANCE',
|
||||
publiclyAccessible: false,
|
||||
hostInstanceType: 'mq.m5.large', // Adjust the instance type as needed
|
||||
subnetIds: [selectedSubnet.subnetId], // Pass a single subnet
|
||||
securityGroups: [rabbitMqSecurityGroup.securityGroupId],
|
||||
users: [
|
||||
{
|
||||
username: 'becknadmin', // Fixed username
|
||||
password: rabbitMqPassword.valueAsString, // Password entered by the user or set from the .env file
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
50
aws-cdk/beckn-cdk/lib/rds-stack-dummy.ts
Normal file
50
aws-cdk/beckn-cdk/lib/rds-stack-dummy.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as rds from 'aws-cdk-lib/aws-rds';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
import cluster from 'cluster';
|
||||
|
||||
export interface RdsStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class RdsStack extends cdk.Stack {
|
||||
public readonly rdsSecret: string;
|
||||
public readonly rdsHost: string;
|
||||
|
||||
constructor(scope: Construct, id: string, props: RdsStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
// Security group for RDS
|
||||
const dbSecurityGroup = new ec2.SecurityGroup(this, 'DatabaseSecurityGroup', {
|
||||
vpc: props.vpc,
|
||||
description: 'Security group for Aurora PostgreSQL database',
|
||||
allowAllOutbound: true,
|
||||
});
|
||||
|
||||
dbSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.vpc.vpcCidrBlock), ec2.Port.tcp(5432), 'Allow Postgres access');
|
||||
|
||||
// Create Aurora PostgreSQL database cluster
|
||||
const cluster = new rds.DatabaseCluster(this, 'AuroraCluster', {
|
||||
engine: rds.DatabaseClusterEngine.auroraPostgres({
|
||||
version: rds.AuroraPostgresEngineVersion.VER_13_15,
|
||||
}),
|
||||
instances: 2,
|
||||
instanceProps: {
|
||||
vpc: props.vpc,
|
||||
vpcSubnets: {
|
||||
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
||||
},
|
||||
securityGroups: [dbSecurityGroup],
|
||||
instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM),
|
||||
},
|
||||
credentials: rds.Credentials.fromGeneratedSecret('dbadmin'),
|
||||
defaultDatabaseName: 'MyDatabase',
|
||||
removalPolicy: cdk.RemovalPolicy.DESTROY, // Destroy cluster when stack is deleted (useful for development)
|
||||
});
|
||||
|
||||
this.rdsHost = cluster.clusterEndpoint.hostname;
|
||||
}
|
||||
}
|
||||
84
aws-cdk/beckn-cdk/lib/rds-stack.ts
Normal file
84
aws-cdk/beckn-cdk/lib/rds-stack.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as rds from 'aws-cdk-lib/aws-rds';
|
||||
import { Construct } from 'constructs';
|
||||
import { ConfigProps } from './config';
|
||||
import cluster from 'cluster';
|
||||
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
|
||||
|
||||
export interface RdsStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
envC: string;
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class RdsStack extends cdk.Stack {
|
||||
public readonly rdsSecret: string;
|
||||
public readonly rdsHost: string;
|
||||
public readonly rdsPassword: string;
|
||||
|
||||
constructor(scope: Construct, id: string, props: RdsStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const vpc = props.vpc;
|
||||
const dbName = props.envC;
|
||||
const rdsUser = props.config.RDS_USER; // take input from user / make it
|
||||
const rdsPassword = this.createPassword();
|
||||
const rdsSecGrpIngress = props.config.CIDR;
|
||||
|
||||
const securityGroupRDS = new ec2.SecurityGroup(this, 'RdsSecurityGroup', {
|
||||
vpc: vpc,
|
||||
allowAllOutbound: true,
|
||||
description: 'Security group for Aurora PostgreSQL database',
|
||||
});
|
||||
|
||||
securityGroupRDS.addIngressRule(
|
||||
ec2.Peer.ipv4(rdsSecGrpIngress),
|
||||
ec2.Port.tcp(5432),
|
||||
"Allow Postgress Access"
|
||||
);
|
||||
|
||||
const creds = new Secret(this, "rdsSecret", {
|
||||
secretObjectValue: {
|
||||
username: cdk.SecretValue.unsafePlainText(rdsUser.toString()),
|
||||
password: cdk.SecretValue.unsafePlainText(rdsPassword.toString()),
|
||||
},
|
||||
});
|
||||
|
||||
const cluster = new rds.DatabaseCluster(this, 'AuroraCluster', {
|
||||
engine: rds.DatabaseClusterEngine.auroraPostgres({
|
||||
version: rds.AuroraPostgresEngineVersion.VER_14_6,
|
||||
}),
|
||||
credentials: rds.Credentials.fromSecret(creds),
|
||||
instances: 1,
|
||||
instanceProps: {
|
||||
vpc: props.vpc,
|
||||
vpcSubnets: {
|
||||
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
||||
},
|
||||
securityGroups: [securityGroupRDS],
|
||||
instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MEDIUM),
|
||||
},
|
||||
defaultDatabaseName: dbName,
|
||||
});
|
||||
|
||||
this.rdsSecret = creds.secretArn;
|
||||
this.rdsHost = cluster.clusterEndpoint.hostname;
|
||||
this.rdsPassword = rdsPassword;
|
||||
|
||||
new cdk.CfnOutput(this, 'RDSPasswordOutput', {
|
||||
value: rdsPassword,
|
||||
exportName: `RDSPassword-${dbName}`,
|
||||
})
|
||||
}
|
||||
|
||||
//generate password function
|
||||
private createPassword(length: number = 12): string {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,-.:;<=>?[]^_`{|}~';
|
||||
let password = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
password += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return password;
|
||||
}
|
||||
}
|
||||
38
aws-cdk/beckn-cdk/lib/redis-stack.ts
Normal file
38
aws-cdk/beckn-cdk/lib/redis-stack.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
|
||||
|
||||
interface RedisStackProps extends cdk.StackProps {
|
||||
vpc: ec2.Vpc;
|
||||
}
|
||||
|
||||
export class RedisStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props: RedisStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
// Security group for ElastiCache
|
||||
const elasticacheSecurityGroup = new ec2.SecurityGroup(this, 'ElastiCacheSecurityGroup', {
|
||||
vpc: props.vpc,
|
||||
description: 'Security group for Redis',
|
||||
allowAllOutbound: true,
|
||||
});
|
||||
|
||||
elasticacheSecurityGroup.addIngressRule(ec2.Peer.ipv4(props.vpc.vpcCidrBlock), ec2.Port.tcp(6379), 'Allow Redis traffic');
|
||||
|
||||
// Redis subnet group
|
||||
const redisSubnetGroup = new elasticache.CfnSubnetGroup(this, 'RedisSubnetGroup', {
|
||||
description: 'Subnet group for Redis cluster',
|
||||
subnetIds: props.vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }).subnetIds,
|
||||
});
|
||||
|
||||
// Redis Cluster
|
||||
new elasticache.CfnCacheCluster(this, 'RedisCluster', {
|
||||
cacheNodeType: 'cache.t3.medium', // Adjust the node type based on your needs
|
||||
engine: 'redis',
|
||||
numCacheNodes: 1,
|
||||
vpcSecurityGroupIds: [elasticacheSecurityGroup.securityGroupId],
|
||||
cacheSubnetGroupName: redisSubnetGroup.ref,
|
||||
});
|
||||
}
|
||||
}
|
||||
76
aws-cdk/beckn-cdk/lib/vpc-stack.ts
Normal file
76
aws-cdk/beckn-cdk/lib/vpc-stack.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as elb from 'aws-cdk-lib/aws-elasticloadbalancingv2';
|
||||
import { ConfigProps } from './config';
|
||||
|
||||
export interface VpcStackProps extends cdk.StackProps {
|
||||
config: ConfigProps;
|
||||
}
|
||||
|
||||
export class VpcStack extends cdk.Stack {
|
||||
public readonly vpc: ec2.Vpc;
|
||||
// public readonly alb: elb.ApplicationLoadBalancer;
|
||||
|
||||
constructor(scope: Construct, id: string, props: VpcStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const config = props.config;
|
||||
|
||||
// Create a new VPC
|
||||
this.vpc = new ec2.Vpc(this, 'beckn-onix-vpc', {
|
||||
maxAzs: config.MAX_AZS, // Maximum number of availability zones
|
||||
cidr: config.CIDR,
|
||||
natGateways: 1, // Single NAT Gateway in the public subnet
|
||||
subnetConfiguration: [
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'Public',
|
||||
subnetType: ec2.SubnetType.PUBLIC,
|
||||
},
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'AppLayer',
|
||||
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Use the newer "PRIVATE_WITH_EGRESS" instead of PRIVATE_WITH_NAT
|
||||
},
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'DatabaseLayer',
|
||||
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Output the VPC CIDR block for other stacks to reference
|
||||
new cdk.CfnOutput(this, 'VpcCidrBlock', {
|
||||
value: this.vpc.vpcCidrBlock,
|
||||
exportName: 'VpcCidrBlock-env', // Export name to reference in other stacks
|
||||
});
|
||||
|
||||
// Output the VPC ID for other stacks
|
||||
new cdk.CfnOutput(this, 'VpcId', {
|
||||
value: this.vpc.vpcId,
|
||||
exportName: 'VpcId', // Export name to reference in other stacks
|
||||
});
|
||||
|
||||
// Output the Public Subnet IDs
|
||||
new cdk.CfnOutput(this, 'PublicSubnetIds', {
|
||||
value: this.vpc.publicSubnets.map(subnet => subnet.subnetId).join(','),
|
||||
exportName: 'PublicSubnetIds', // Export name to reference in other stacks
|
||||
});
|
||||
|
||||
// Output the App Layer Subnet IDs (for application instances or services)
|
||||
new cdk.CfnOutput(this, 'AppLayerSubnetIds', {
|
||||
value: this.vpc.selectSubnets({ subnetGroupName: 'AppLayer' }).subnetIds.join(','),
|
||||
exportName: 'AppLayerSubnetIds', // Export name to reference in other stacks
|
||||
});
|
||||
|
||||
// Output the Database Layer Subnet IDs (for database instances)
|
||||
new cdk.CfnOutput(this, 'DatabaseSubnetIds', {
|
||||
value: this.vpc.selectSubnets({ subnetGroupName: 'DatabaseLayer' }).subnetIds.join(','),
|
||||
exportName: 'DatabaseSubnetIds', // Export name to reference in other stacks
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user