diff --git a/.github/workflows/iac.yml b/.github/workflows/iac.yml deleted file mode 100644 index 7fc7e45..0000000 --- a/.github/workflows/iac.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: iac.yml -on: - -jobs: diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e2f653 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +![Figure 2.1.1 Azure cloud infrastructure deployed automatically via Terraform](https://github.com/user-attachments/assets/1c17cd6d-572b-4426-9d60-d5d1800c0c2d) diff --git a/infrastructure/dev/docker-compose.yaml b/infrastructure/dev/docker-compose.yaml index bc16bb3..df9c523 100644 --- a/infrastructure/dev/docker-compose.yaml +++ b/infrastructure/dev/docker-compose.yaml @@ -1,34 +1,9 @@ services: - # API GATEWAY - - kong-cp: - image: '${GW_IMAGE:-kong/kong-gateway:3.11.0.2}' - restart: on-failure - environment: - KONG_DATABASE: off - KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yaml - KONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl - KONG_ADMIN_GUI_LISTEN: 0.0.0.0:8002, 0.0.0.0:8445 ssl - KONG_ADMIN_GUI_URL: http://${GW_HOST:-localhost}:8002 - KONG_PASSWORD: handyshake - ports: - - "8000:8000" # Proxy HTTP - - "8443:8443" # Proxy HTTPS - - "8001:8001" # Admin API HTTP - - "8444:8444" # Admin API HTTPS - - "8002:8002" # Kong Manager HTTP - - "8445:8445" # Kong Manager HTTPS - networks: - - backend-network - volumes: - - ./kong/kong.yaml:/kong/declarative/kong.yaml - command: kong start - # MICROSERVICES storage-service: - image: storage-service:dev + image: acrdevopsprojectprod.azurecr.io/storage-service:v1.0 build: context: ../../storage-service dockerfile: Dockerfile @@ -38,6 +13,8 @@ services: RABBITMQ_HOST: rabbitmq BUCKET_ENDPOINT: http://minio:9000 AWS_BUCKET_NAME : devops + ports: + - "8080:8080" networks: - backend-network - db-network @@ -51,7 +28,7 @@ services: start_interval: 5s user-service: - image: user-service:dev + image: acrdevopsprojectprod.azurecr.io/user-service:v1.0 build: context: ../../user-service dockerfile: Dockerfile @@ -59,7 +36,9 @@ services: environment: SERVER_PORT: 8080 RABBITMQ_HOST: rabbitmq - POSTGRESQL_URL: jdbc:postgresql://postgresql/devops + POSTGRESQL_URI: jdbc:postgresql://postgresql/devops + ports: + - "8081:8080" networks: - backend-network - db-network @@ -151,7 +130,7 @@ services: PGADMIN_DEFAULT_EMAIL: kacper@kacper.pl PGADMIN_DEFAULT_PASSWORD: kacper ports: - - "8080:80" + - "8880:80" networks: - db-network volumes: @@ -161,7 +140,7 @@ services: image: mongo-express:latest restart: on-failure ports: - - "8081:8081" + - "8881:8081" environment: ME_CONFIG_MONGODB_ADMINUSERNAME: kacper ME_CONFIG_MONGODB_ADMINPASSWORD: kacper diff --git a/infrastructure/prod/kubernetes/argocd-app-of-apps/config-rabbitmq.yaml b/infrastructure/prod/kubernetes/argocd-app-of-apps/config-rabbitmq.yaml index 7250ae4..672536c 100644 --- a/infrastructure/prod/kubernetes/argocd-app-of-apps/config-rabbitmq.yaml +++ b/infrastructure/prod/kubernetes/argocd-app-of-apps/config-rabbitmq.yaml @@ -25,6 +25,5 @@ spec: app.kubernetes.io/component: rabbitmq-operator app.kubernetes.io/name: rabbitmq-system app.kubernetes.io/part-of: rabbitmq - istio-injection: enabled # To inject sidecar proxy automatically we need set this label syncOptions: - CreateNamespace=true \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-base.yaml b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-base.yaml index 2dd69fb..1a01777 100644 --- a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-base.yaml +++ b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-base.yaml @@ -10,7 +10,7 @@ spec: source: chart: base repoURL: https://istio-release.storage.googleapis.com/charts - targetRevision: 1.28.1 + targetRevision: 1.28.2 helm: releaseName: istio-base parameters: diff --git a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-d.yaml b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-d.yaml index 033e714..4bbfb47 100644 --- a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-d.yaml +++ b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-d.yaml @@ -10,7 +10,7 @@ spec: source: chart: istiod repoURL: https://istio-release.storage.googleapis.com/charts - targetRevision: 1.28.1 + targetRevision: 1.28.2 helm: releaseName: istiod destination: diff --git a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-egress-controller.yaml b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-egress-controller.yaml index 863745d..010d07d 100644 --- a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-egress-controller.yaml +++ b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-egress-controller.yaml @@ -12,7 +12,7 @@ spec: source: chart: gateway repoURL: https://istio-release.storage.googleapis.com/charts - targetRevision: 1.28.1 + targetRevision: 1.28.2 helm: releaseName: istio-egressgateway values: | diff --git a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-ingress-controller.yaml b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-ingress-controller.yaml index 32b53f1..629cb32 100644 --- a/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-ingress-controller.yaml +++ b/infrastructure/prod/kubernetes/argocd-app-of-apps/helm-istio-ingress-controller.yaml @@ -12,7 +12,7 @@ spec: source: chart: gateway repoURL: https://istio-release.storage.googleapis.com/charts - targetRevision: 1.28.1 + targetRevision: 1.28.2 helm: releaseName: istio-ingressgateway values: | diff --git a/infrastructure/prod/kubernetes/external-secrets-manifests/key-vault-external-secrets.yaml b/infrastructure/prod/kubernetes/external-secrets-manifests/key-vault-external-secrets.yaml index 7c1fecd..ea6ff8f 100644 --- a/infrastructure/prod/kubernetes/external-secrets-manifests/key-vault-external-secrets.yaml +++ b/infrastructure/prod/kubernetes/external-secrets-manifests/key-vault-external-secrets.yaml @@ -11,7 +11,7 @@ spec: refreshInterval: 0h5m0s target: name: cloudflare-api-token-secret - creationPolicy: Owner # Creates Kubernetes secret if ExternalSecret created + creationPolicy: Owner # Creates Kubernetes secret if ExternalSecret created data: - secretKey: api-token remoteRef: @@ -53,19 +53,78 @@ spec: template: type: kubernetes.io/dockerconfigjson data: - .dockerconfigjson: | + .dockerconfigjson: | # Token username is the same as ACR name { "auths": { - "acrdevopsprojectprod.azurecr.io": { + "{{ .acrName }}.azurecr.io": { "username": "{{ .token }}", "password": "{{ .password }}" } } } data: + - secretKey: acrName + remoteRef: + key: secret/acr-name - secretKey: token remoteRef: key: secret/aks-acr-token - secretKey: password remoteRef: - key: secret/aks-acr-password \ No newline at end of file + key: secret/aks-acr-password +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: postgresql-credentials + namespace: prod +spec: + secretStoreRef: + kind: ClusterSecretStore + name: azure-cluster-secret-store + refreshPolicy: Periodic + refreshInterval: 0h5m0s + target: + name: postgresql-credentials + creationPolicy: Owner + template: # Here we are using templating engine to create secret ready to be used in Spring application + engineVersion: v2 + data: # Complete URI for JDBC with TLS required + uri: "jdbc:postgresql://{{ .uri }}/devops?&sslmode=require" + username: "{{ .username }}" + password: "{{ .password }}" + data: + - secretKey: uri + remoteRef: + key: secret/postgresql-uri + - secretKey: username + remoteRef: + key: secret/postgresql-username + - secretKey: password + remoteRef: + key: secret/postgresql-password +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: cloudflare-r2-credentials + namespace: prod +spec: + secretStoreRef: + kind: ClusterSecretStore + name: azure-cluster-secret-store + refreshPolicy: Periodic + refreshInterval: 0h5m0s + target: + name: cloudflare-r2-credentials + creationPolicy: Owner + data: + - secretKey: apiUri + remoteRef: + key: secret/cloudflare-r2-api-uri + - secretKey: accessKeyId + remoteRef: + key: secret/cloudflare-r2-account-id + - secretKey: secretAccessKey + remoteRef: + key: secret/cloudflare-api-token \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/istio-manifests/mtls-lockdown-peer-authentication.yaml b/infrastructure/prod/kubernetes/istio-manifests/mtls-lockdown-peer-authentication.yaml index 131bc99..e86cadf 100644 --- a/infrastructure/prod/kubernetes/istio-manifests/mtls-lockdown-peer-authentication.yaml +++ b/infrastructure/prod/kubernetes/istio-manifests/mtls-lockdown-peer-authentication.yaml @@ -5,4 +5,4 @@ metadata: namespace: istio-system spec: mtls: - mode: PERMISSIVE # Lock down workloads in all namespaces to prioritise accept mTLS traffic \ No newline at end of file + mode: STRICT # Lock down workloads in all namespaces to prioritise accept mTLS traffic \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/istio-manifests/rabbitmq-virtual-service.yaml b/infrastructure/prod/kubernetes/istio-manifests/rabbitmq-virtual-service.yaml index 0721ddd..16bff9e 100644 --- a/infrastructure/prod/kubernetes/istio-manifests/rabbitmq-virtual-service.yaml +++ b/infrastructure/prod/kubernetes/istio-manifests/rabbitmq-virtual-service.yaml @@ -28,9 +28,16 @@ spec: - devops-project-cluster.rabbitmq-system.svc.cluster.local tcp: - match: - - port: 5672 + - port: 5672 # TCP AMQP route: - destination: host: devops-project-cluster.rabbitmq-system.svc.cluster.local port: - number: 5672 \ No newline at end of file + number: 5672 + - match: + - port: 15692 # TCP Prometheus port + route: + - destination: + host: devops-project-cluster.rabbitmq-system.svc.cluster.local + port: + number: 15692 \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/istio-manifests/storage-service-virtual-service.yaml b/infrastructure/prod/kubernetes/istio-manifests/storage-service-virtual-service.yaml index 12cc455..e77b174 100644 --- a/infrastructure/prod/kubernetes/istio-manifests/storage-service-virtual-service.yaml +++ b/infrastructure/prod/kubernetes/istio-manifests/storage-service-virtual-service.yaml @@ -15,13 +15,19 @@ spec: route: - destination: host: storage-service.prod.svc.cluster.local + port: + number: 8080 subset: v1 weight: 100 # We declare that all traffic is routed to v1.0 version - destination: host: storage-service.prod.svc.cluster.local + port: + number: 8080 subset: v2 weight: 0 - destination: host: storage-service.prod.svc.cluster.local + port: + number: 8080 subset: v3 weight: 0 \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/istio-manifests/user-service-virtual-service.yaml b/infrastructure/prod/kubernetes/istio-manifests/user-service-virtual-service.yaml index bfab57a..2c6c446 100644 --- a/infrastructure/prod/kubernetes/istio-manifests/user-service-virtual-service.yaml +++ b/infrastructure/prod/kubernetes/istio-manifests/user-service-virtual-service.yaml @@ -15,13 +15,19 @@ spec: route: - destination: host: user-service.prod.svc.cluster.local + port: + number: 8080 subset: v1 weight: 100 # We declare that all traffic is routed to v1.0 version - destination: host: user-service.prod.svc.cluster.local + port: + number: 8080 subset: v2 weight: 0 - destination: host: user-service.prod.svc.cluster.local + port: + number: 8080 subset: v3 weight: 0 \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/rabbitmq-manifests/rabbitmq-cluster-config.yaml b/infrastructure/prod/kubernetes/rabbitmq-manifests/rabbitmq-cluster-config.yaml index d30da2f..14b8572 100644 --- a/infrastructure/prod/kubernetes/rabbitmq-manifests/rabbitmq-cluster-config.yaml +++ b/infrastructure/prod/kubernetes/rabbitmq-manifests/rabbitmq-cluster-config.yaml @@ -3,14 +3,15 @@ kind: RabbitmqCluster metadata: name: devops-project-cluster namespace: rabbitmq-system - labels: - istio-injection: enabled # To inject sidecar proxy automatically we need to set this label spec: replicas: 1 override: statefulSet: spec: template: + metadata: + labels: # To inject sidecar proxy automatically we need to set this label (Pod level) That way we prevent injecting to deployments where there is no need + sidecar.istio.io/inject: "true" # Same problem as with ArgoCD but I found solution :) spec: containers: - name: rabbitmq diff --git a/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-deployment-v1.0.yaml b/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-deployment-v1.0.yaml index c89c60c..04e7d22 100644 --- a/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-deployment-v1.0.yaml +++ b/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-deployment-v1.0.yaml @@ -18,12 +18,30 @@ spec: app: storage-service version: v1.0 spec: - imagePullSecrets: # Setting credentials to Azure Container Registry, synchronized with Key Vault - - name: azurecr-credentials containers: - name: storage-service - image: storage-service:dev + image: acrdevopsprojectprod.azurecr.io/storage-service:v1.0 imagePullPolicy: IfNotPresent + env: + - name: SERVER_PORT + value: "8080" + - name: RABBITMQ_HOST + value: rabbitmq.rabbitmq-system.svc.cluster.local + - name: BUCKET_ENDPOINT + valueFrom: + secretKeyRef: + name: cloudflare-r2-credentials + key: apiUri + - name: BUCKET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: cloudflare-r2-credentials + key: accessKeyId + - name: BUCKET_SECRET_KEY + valueFrom: + secretKeyRef: + name: cloudflare-r2-credentials + key: secretAccessKey ports: - containerPort: 8080 protocol: TCP @@ -45,5 +63,7 @@ spec: port: 8080 initialDelaySeconds: 15 periodSeconds: 10 - serviceAccountName: storage-service - restartPolicy: Always \ No newline at end of file + imagePullSecrets: # Setting credentials to Azure Container Registry, synchronized with Key Vault + - name: azurecr-credentials + restartPolicy: Always + serviceAccountName: storage-service \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-network-policy-v1.0.yaml b/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-network-policy-v1.0.yaml deleted file mode 100644 index 31c750d..0000000 --- a/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-network-policy-v1.0.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: storage-service-v1.0 - namespace: prod -spec: - podSelector: - matchLabels: - app: storage-service - version: v1.0 - policyTypes: - - Ingress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: prod - ports: - - protocol: TCP - port: 8080 \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-rbac.yaml b/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-rbac.yaml new file mode 100644 index 0000000..17ab0a3 --- /dev/null +++ b/infrastructure/prod/kubernetes/storage-service-manifests/storage-service-rbac.yaml @@ -0,0 +1,22 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: storage-service-read-secrets + namespace: prod +rules: + - apiGroups: [ "" ] + resources: [ "secrets" ] + verbs: [get, list] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: storage-service-read-secrets + namespace: prod +subjects: + - kind: ServiceAccount + name: storage-service +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: storage-service-read-secrets \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/user-service-manifests/user-service-deployment-v1.0.yaml b/infrastructure/prod/kubernetes/user-service-manifests/user-service-deployment-v1.0.yaml index 7d017f6..11c6eb6 100644 --- a/infrastructure/prod/kubernetes/user-service-manifests/user-service-deployment-v1.0.yaml +++ b/infrastructure/prod/kubernetes/user-service-manifests/user-service-deployment-v1.0.yaml @@ -18,12 +18,30 @@ spec: app: user-service version: v1.0 spec: - imagePullSecrets: # Setting credentials to Azure Container Registry, synchronized with Key Vault - - name: azurecr-credentials containers: - name: user-service - image: user-service:dev + image: acrdevopsprojectprod.azurecr.io/user-service:v1.0 imagePullPolicy: IfNotPresent + env: + - name: SERVER_PORT + value: "8080" + - name: RABBITMQ_HOST + value: rabbitmq.rabbitmq-system.svc.cluster.local + - name: POSTGRESQL_URI + valueFrom: + secretKeyRef: + name: postgresql-credentials + key: uri + - name: POSTGRESQL_USERNAME + valueFrom: + secretKeyRef: + name: postgresql-credentials + key: username + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: postgresql-credentials + key: password ports: - containerPort: 8080 protocol: TCP @@ -45,5 +63,7 @@ spec: port: 8080 initialDelaySeconds: 15 periodSeconds: 10 - serviceAccountName: user-service - restartPolicy: Always \ No newline at end of file + imagePullSecrets: # Setting credentials to Azure Container Registry, synchronized with Key Vault + - name: azurecr-credentials + restartPolicy: Always + serviceAccountName: user-service \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/user-service-manifests/user-service-network-policy-v1.0.yaml b/infrastructure/prod/kubernetes/user-service-manifests/user-service-network-policy-v1.0.yaml deleted file mode 100644 index 04308f4..0000000 --- a/infrastructure/prod/kubernetes/user-service-manifests/user-service-network-policy-v1.0.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: user-service-v1.0 - namespace: prod -spec: - podSelector: - matchLabels: - app: user-service - version: v1.0 - policyTypes: - - Ingress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: prod - ports: - - protocol: TCP - port: 8080 \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/user-service-manifests/user-service-rbac.yaml b/infrastructure/prod/kubernetes/user-service-manifests/user-service-rbac.yaml new file mode 100644 index 0000000..ae53689 --- /dev/null +++ b/infrastructure/prod/kubernetes/user-service-manifests/user-service-rbac.yaml @@ -0,0 +1,22 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: user-service-read-secrets + namespace: prod +rules: + - apiGroups: [ "" ] + resources: [ "secrets" ] + verbs: [get, list] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: user-service-read-secrets + namespace: prod +subjects: + - kind: ServiceAccount + name: user-service +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: user-service-read-secrets \ No newline at end of file diff --git a/infrastructure/prod/kubernetes/values.yaml b/infrastructure/prod/kubernetes/values.yaml index b8f2b33..91310c0 100644 --- a/infrastructure/prod/kubernetes/values.yaml +++ b/infrastructure/prod/kubernetes/values.yaml @@ -4,6 +4,8 @@ configs: cm: statusbadge.enabled: true # -- Enable Status Badge server: # We need to apply additional config to ArgoCD helm chart to proxy traffic via Istio gateway + podLabels: # We are injecting Istio Envoy Proxy Sidecar here using annotation for pods. Only for ArgoCD Server because is used with Ingress Gateway for now + sidecar.istio.io/inject: "true" # If you don't want a problem with installation remember: If you need to pass some bool value as string use quotation marks "" :) extraArgs: - --staticassets - /shared/app diff --git a/infrastructure/prod/terraform/main.tf b/infrastructure/prod/terraform/main.tf index 47f6bb3..b8fa9a6 100644 --- a/infrastructure/prod/terraform/main.tf +++ b/infrastructure/prod/terraform/main.tf @@ -35,7 +35,7 @@ resource "cloudflare_r2_custom_domain" "devops_r2_custom_domain" { domain = "r2storage-${local.env}.kacperklimas.com" enabled = true zone_id = var.cloudflare_dns_zone_id - min_tls = "1.2" + min_tls = "1.2" # What's interesting R2 resource block use TLS 1.0 as default minimum version which is not recommended and can cause major security issues, so we need to change to TLS 1.2 } /* AZURE */ @@ -155,7 +155,7 @@ resource "azurerm_virtual_network_gateway" "vpn_gateway" { resource_group_name = module.azure_resource_group.name type = "Vpn" generation = "Generation1" - sku = "VpnGw2AZ" + sku = "VpnGw2AZ" # Zone-redundant gateway ip_configuration { public_ip_address_id = azurerm_public_ip.vpn_public_ip.id subnet_id = module.azure_management_vnet.subnets["vpnsubnet"].resource_id @@ -165,7 +165,7 @@ resource "azurerm_virtual_network_gateway" "vpn_gateway" { vpn_client_protocols = ["IkeV2", "OpenVPN"] address_space = ["172.16.0.0/24"] root_certificate { - name = "devopsCA" + name = "devopsCA" # Azure documentation says if we want to use self generated certificate we need to take part without --BEGIN CERTIFICATE-- and --END CERTIFICATE-- lines, so we use regular expressions to do that. public_cert_data = replace(file(var.azure_vpn_path_to_cert), "/-.*-/", "") } } @@ -200,13 +200,6 @@ module "azure_management_vnet" { id = module.azure_management_vnet_nsg.resource_id } } - # "dbsubnet" = { - # name = "DatabaseInstancesSubnet" - # address_prefixes = ["10.1.2.0/24"] - # network_security_group = { - # id = module.azure_management_vnet_nsg.resource_id - # } - # } } tags = var.azure_application_tags } @@ -464,6 +457,10 @@ module "azure_aks" { tags = var.azure_application_tags } +locals { # Local variable to pass in ACR config and Secret config + aks_acr_token = "aks-token" +} + # Azure Container Registry module "azure_container_registry" { source = "Azure/avm-res-containerregistry-registry/azurerm" @@ -485,10 +482,10 @@ module "azure_container_registry" { aksscope = { name = "aks-scope" # Authorization read only (Pulling images, reading statuses etc) actions = ["repositories/*/content/read", "repositories/*/metadata/read"] - description = "Read only all repositories" + description = "Read-only all repositories" registry_tokens = { akstoken = { - name = "aks-token" + name = local.aks_acr_token passwords = { password1 = { # Expiration date for token password expiry = "2026-12-31T00:00:00Z" @@ -520,28 +517,10 @@ data "azurerm_container_registry" "azure_container_registry" { } locals { - keyvault_private_endpoint_ip = data.azurerm_private_endpoint_connection.key_vault_private_endpoint.private_service_connection[0].private_ip_address - containerregistry_private_endpoint_ip = data.azurerm_private_endpoint_connection.container_registry_private_endpoint.private_service_connection[0].private_ip_address - postgresql_private_endpoint_ip = data.azurerm_private_endpoint_connection.postgresql_private_endpoint.private_service_connection[0].private_ip_address - container_registry_aks_password = module.azure_container_registry.scope_maps["aksscope"].registry_token_passwords["akstoken"].password1[0].value -} - -data "azurerm_private_endpoint_connection" "key_vault_private_endpoint" { - name = "KeyVaultPrivateEndpoint" - resource_group_name = module.azure_resource_group.name - depends_on = [module.devops_key_vault] -} - -data "azurerm_private_endpoint_connection" "container_registry_private_endpoint" { - name = "ContainerRegistryPrivateEndpoint" - resource_group_name = module.azure_resource_group.name - depends_on = [module.azure_container_registry] -} - -data "azurerm_private_endpoint_connection" "postgresql_private_endpoint" { - name = "PostgresqlPrivateEndpoint" - resource_group_name = module.azure_resource_group.name - depends_on = [module.devops_postgresql] + keyvault_private_endpoint_ip = data.azurerm_private_endpoint_connection.key_vault_private_endpoint.private_service_connection[0].private_ip_address + postgresql_private_endpoint_ip = data.azurerm_private_endpoint_connection.postgresql_private_endpoint.private_service_connection[0].private_ip_address + container_registry_main_endpoint_ip = data.azurerm_network_interface.container_registry_main_private_endpoint.private_ip_addresses[1] # This is a list, fist address is matching .data subdomain so if we need to retrieve address for main subdomain it must select second address in a list + container_registry_aks_password = module.azure_container_registry.scope_maps["aksscope"].registry_token_passwords["akstoken"].password1[0].value } # Azure Key Vault @@ -584,12 +563,24 @@ module "devops_key_vault" { description = "KeyVaultDefaultClient" } } - secrets = { # Create secret for Kubernetes external dns and cert manager + secrets = { # Create secret for Kubernetes external dns and cert manager whole workload + cloudflare_r2_api_uri = { + name = "cloudflare-r2-api-uri" + tags = var.azure_application_tags + } + cloudflare_r2_account_id = { + name = "cloudflare-r2-account-id" + tags = var.azure_application_tags + } cloudflare_api_token = { name = "cloudflare-api-token" tags = var.azure_application_tags } - aks_registry_login = { + acr_name = { + name = "acr-name" + tags = var.azure_application_tags + } + aks_registry_token = { name = "aks-acr-token" tags = var.azure_application_tags } @@ -601,8 +592,8 @@ module "devops_key_vault" { name = "postgresql-uri" tags = var.azure_application_tags } - postgresql_user = { - name = "postgresql-user" + postgresql_username = { + name = "postgresql-username" tags = var.azure_application_tags } postgresql_password = { @@ -611,12 +602,15 @@ module "devops_key_vault" { } } secrets_value = { - cloudflare_api_token = var.cloudflare_api_token - aks_registry_login = module.azure_container_registry.name - aks_registry_password = local.container_registry_aks_password - postgresql_uri = data.azurerm_postgresql_flexible_server.devops_postgresql.fqdn - postgresql_user = data.azurerm_postgresql_flexible_server.devops_postgresql.administrator_login - postgresql_password = random_password.postgresql_admin_password.result + cloudflare_r2_api_uri = "https://${cloudflare_r2_custom_domain.devops_r2_custom_domain.domain}/${cloudflare_r2_bucket.devops_r2_bucket.name}" + cloudflare_r2_account_id = var.cloudflare_account_id + cloudflare_api_token = var.cloudflare_api_token + acr_name = module.azure_container_registry.name + aks_registry_token = local.aks_acr_token # Here we need to pass ACR token name + aks_registry_password = local.container_registry_aks_password + postgresql_uri = data.azurerm_postgresql_flexible_server.devops_postgresql.fqdn + postgresql_username = data.azurerm_postgresql_flexible_server.devops_postgresql.administrator_login + postgresql_password = random_password.postgresql_admin_password.result } tags = var.azure_application_tags } @@ -628,7 +622,7 @@ module "devops_postgresql" { name = "${module.azure_naming.postgresql_server.name}-${local.env}" resource_group_name = module.azure_resource_group.name location = var.azure_region - server_version = "16" # Postgresql 18 + server_version = "16" # Postgresql 16 sku_name = "GP_Standard_D2ds_v4" # 2 vCores, 8 GiB memory, 3750 max iops for Server VM. Depends on Region and current availability storage_mb = "32768" # 32 GB on SSD Disk authentication = { @@ -641,7 +635,7 @@ module "devops_postgresql" { high_availability = { mode = "SameZone" } - zone = "1" # Need to choose AZ for correct provisioning + zone = "1" # Need to choose AZ for correct provisioning databases = { devops = { name = "devops" @@ -663,12 +657,31 @@ module "devops_postgresql" { tags = var.azure_application_tags } +# In this section we need to retrieve IPv4 addresses from Private Endpoint configurations data "azurerm_postgresql_flexible_server" "devops_postgresql" { name = module.devops_postgresql.name resource_group_name = module.azure_resource_group.name depends_on = [module.devops_postgresql] } +data "azurerm_private_endpoint_connection" "key_vault_private_endpoint" { + name = "KeyVaultPrivateEndpoint" + resource_group_name = module.azure_resource_group.name + depends_on = [module.devops_key_vault] +} + +data "azurerm_private_endpoint_connection" "postgresql_private_endpoint" { + name = "PostgresqlPrivateEndpoint" + resource_group_name = module.azure_resource_group.name + depends_on = [module.devops_postgresql] +} + +data "azurerm_network_interface" "container_registry_main_private_endpoint" { + name = "containerregistry-${module.azure_naming.network_interface.name}${local.env}" + resource_group_name = module.azure_resource_group.name + depends_on = [module.azure_container_registry] +} + # Private DNS Zones (These are very important because we can use TLS protocol in isolated private Azure network without exposing endpoints outside. Azure usually provides TLS wildcard certs that we can use with resource name as subdomain) module "devops_key_vault_private_dns_zone" { source = "Azure/avm-res-network-privatednszone/azurerm" @@ -700,13 +713,13 @@ module "devops_container_registry_private_dns_zone" { version = "0.4.3" domain_name = "azurecr.io" parent_id = module.azure_resource_group.resource_id - a_records = { - acr = { - name = module.azure_container_registry.name - ttl = 5 - ip_addresses = [local.containerregistry_private_endpoint_ip] - } - } + # a_records = { # I don't know why ACR module enter primary (data) Private DNS record automatically, I had some bugs with that because methods used in other resources (DB, Key Vault) are using one IPv4 address and one domain per resource. I couldn't pull images from registry because DNS records weren't configured correctly + # acr_io = { + # name = module.azure_container_registry.name + # ttl = 5 + # ip_addresses = [local.container_registry_main_endpoint_ip] + # } + # } virtual_network_links = { aks = { vnetlinkname = "aksvnetlink" @@ -725,13 +738,13 @@ module "devops_postgresql_private_dns_zone" { version = "0.4.3" domain_name = "postgres.database.azure.com" parent_id = module.azure_resource_group.resource_id - a_records = { - db = { - name = module.devops_postgresql.name - ttl = 5 - ip_addresses = [local.postgresql_private_endpoint_ip] - } - } + # a_records = { # Same problem as with ACR + # db = { + # name = module.devops_postgresql.name + # ttl = 5 + # ip_addresses = [local.postgresql_private_endpoint_ip] + # } + # } virtual_network_links = { aks = { vnetlinkname = "aksvnetlink" diff --git a/pom.xml b/pom.xml index 4bcff74..f7a4336 100644 --- a/pom.xml +++ b/pom.xml @@ -36,10 +36,6 @@ org.springframework.boot spring-boot-starter - - org.springframework - spring-webmvc - org.springframework.boot spring-boot-starter-web diff --git a/storage-service/src/main/java/com/devops/storageservice/config/SecurityConfig.java b/storage-service/src/main/java/com/devops/storageservice/config/SecurityConfig.java index 41ce17a..8bd4068 100644 --- a/storage-service/src/main/java/com/devops/storageservice/config/SecurityConfig.java +++ b/storage-service/src/main/java/com/devops/storageservice/config/SecurityConfig.java @@ -2,10 +2,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Configuration @EnableWebSecurity @@ -17,11 +23,26 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement((sessionManagement) -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) + .cors(Customizer.withDefaults()) .authorizeHttpRequests((requests) -> requests - .requestMatchers("/**") + .requestMatchers("/actuator/**", "/api/**") .permitAll() .anyRequest().authenticated() ).csrf((csrf) -> csrf.disable()) .build(); } -} + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOriginPatterns(List.of("http://localhost:*", "https://devops.kacperklimas.com")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); + config.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } +} \ No newline at end of file diff --git a/storage-service/src/main/java/com/devops/storageservice/config/WebConfig.java b/storage-service/src/main/java/com/devops/storageservice/config/WebConfig.java deleted file mode 100644 index e0a38d0..0000000 --- a/storage-service/src/main/java/com/devops/storageservice/config/WebConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.devops.storageservice.config; - -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Component -@EnableWebMvc -public class WebConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("http://localhost:8501") - .allowedMethods("GET", "POST") - .allowedHeaders("*") - .allowCredentials(true); - } -} diff --git a/storage-service/src/main/java/com/devops/storageservice/controller/StorageController.java b/storage-service/src/main/java/com/devops/storageservice/controller/StorageController.java index b5eb62f..62d7ef5 100644 --- a/storage-service/src/main/java/com/devops/storageservice/controller/StorageController.java +++ b/storage-service/src/main/java/com/devops/storageservice/controller/StorageController.java @@ -5,10 +5,7 @@ import lombok.RequiredArgsConstructor; import com.devops.storageservice.service.StorageService; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.net.URISyntaxException; @@ -19,6 +16,13 @@ public class StorageController { private final StorageService storageService; + @GetMapping + public ResponseEntity helloStorage() { + return ResponseEntity + .ok() + .body("Hello from storage-service v1 :)"); + } + @PostMapping("/file") ResponseEntity fileUploadRequest(@RequestBody FileUploadRequestDto fileUploadRequestDto) throws URISyntaxException { FileUploadResponseDto fileUploadResponseDto = storageService.fileUploadRequest(fileUploadRequestDto); diff --git a/storage-service/src/main/java/com/devops/storageservice/service/StorageService.java b/storage-service/src/main/java/com/devops/storageservice/service/StorageService.java index 6d86aa2..6e71dbf 100644 --- a/storage-service/src/main/java/com/devops/storageservice/service/StorageService.java +++ b/storage-service/src/main/java/com/devops/storageservice/service/StorageService.java @@ -13,7 +13,7 @@ @RequiredArgsConstructor public class StorageService { - @Qualifier("BlobStorage") + @Qualifier("R2Storage") private final StorageRepository storageRepository; public FileUploadResponseDto fileUploadRequest(FileUploadRequestDto fileUploadRequestDto) { diff --git a/storage-service/src/main/resources/application.yaml b/storage-service/src/main/resources/application.yaml index 87eefed..d7a9597 100644 --- a/storage-service/src/main/resources/application.yaml +++ b/storage-service/src/main/resources/application.yaml @@ -3,15 +3,18 @@ spring: name: "storage-service" cloud: aws: + credentials: + access-key: "${BUCKET_ACCESS_KEY}:kacperkacper" + secret-key: "${BUCKET_SECRET_KEY}:kacperkacper" s3: endpoint: "${BUCKET_ENDPOINT:http://localhost:9000}" - region: "us-east-1" # Auto region for Minio and R2 + region: "${BUCKET_REGION}:us-east-1" # Auto region for Minio and R2 rabbitmq: username: "${RABBITMQ_USERNAME:kacper}" password: "${RABBITMQ_PASSWORD:kacper}" + virtual-host: "${RABBITMQ_VHOST:rabbitmq}" host: "${RABBITMQ_HOST:localhost}" port: "${RABBITMQ_PORT:5672}" - virtual-host: "${RABBITMQ_VHOST:rabbitmq}" template: retry: enabled: true diff --git a/user-service/src/main/java/com/devops/userservice/config/SecurityConfig.java b/user-service/src/main/java/com/devops/userservice/config/SecurityConfig.java index 5987d21..8a47020 100644 --- a/user-service/src/main/java/com/devops/userservice/config/SecurityConfig.java +++ b/user-service/src/main/java/com/devops/userservice/config/SecurityConfig.java @@ -2,10 +2,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @Configuration @EnableWebSecurity @@ -17,11 +23,26 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement((sessionManagement) -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) + .cors(Customizer.withDefaults()) .authorizeHttpRequests((requests) -> requests - .requestMatchers("/**") + .requestMatchers("/actuator/**", "/api/**") .permitAll() .anyRequest().authenticated() ).csrf((csrf) -> csrf.disable()) .build(); } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOriginPatterns(List.of("http://localhost:*", "https://devops.kacperklimas.com")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); + config.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } } diff --git a/user-service/src/main/java/com/devops/userservice/config/WebConfig.java b/user-service/src/main/java/com/devops/userservice/config/WebConfig.java deleted file mode 100644 index 50a4879..0000000 --- a/user-service/src/main/java/com/devops/userservice/config/WebConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.devops.userservice.config; - -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Component -@EnableWebMvc -public class WebConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("http://localhost:8500") - .allowedMethods("GET", "POST") - .allowedHeaders("*") - .allowCredentials(true); - } -} diff --git a/user-service/src/main/java/com/devops/userservice/controller/UserController.java b/user-service/src/main/java/com/devops/userservice/controller/UserController.java index 23ac0d9..324d77d 100644 --- a/user-service/src/main/java/com/devops/userservice/controller/UserController.java +++ b/user-service/src/main/java/com/devops/userservice/controller/UserController.java @@ -4,10 +4,7 @@ import com.devops.sharedresources.dto.UserDto; import com.devops.userservice.service.UserService; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/user") @@ -16,6 +13,13 @@ public class UserController { private final UserService userService; + @GetMapping + public ResponseEntity helloUser() { + return ResponseEntity + .ok() + .body("Hello from user-service v1 :)"); + } + @PostMapping public ResponseEntity createUser(@RequestBody UserDto userDto) { userService.createUser(userDto.getUsername(), userDto.getEmail()); diff --git a/user-service/src/main/resources/application.yaml b/user-service/src/main/resources/application.yaml index 82ef30b..37d9cff 100644 --- a/user-service/src/main/resources/application.yaml +++ b/user-service/src/main/resources/application.yaml @@ -4,7 +4,7 @@ spring: datasource: username: "${POSTGRESQL_USERNAME:kacper}" password: "${POSTGRESQL_PASSWORD:kacper}" - url: "${POSTGRESQL_URL:jdbc:postgresql://localhost/devops}" + url: "${POSTGRESQL_URI:jdbc:postgresql://localhost/devops}" driver-class-name: org.postgresql.Driver jpa: hibernate: