diff --git a/appointment-frontend/.dockerignore b/appointment-frontend/.dockerignore new file mode 100644 index 000000000..e0a57b287 --- /dev/null +++ b/appointment-frontend/.dockerignore @@ -0,0 +1,2 @@ +**/node_modules +**/dist diff --git a/appointment-frontend/Dockerfile b/appointment-frontend/Dockerfile new file mode 100644 index 000000000..8a2d887af --- /dev/null +++ b/appointment-frontend/Dockerfile @@ -0,0 +1,15 @@ +FROM docker-remote.artifacts.developer.gov.bc.ca/node:10 as build-stage +ENV NODE_ENV=prod +ENV VUE_APP_PATH=/ +WORKDIR /app +COPY ./package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM docker-remote.artifacts.developer.gov.bc.ca/nginx:1.18.0 as production-stage +COPY nginx.conf /etc/nginx/nginx.conf +RUN mkdir /app +COPY --from=build-stage /app/dist /app +EXPOSE 8080:8080 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/appointment-frontend/nginx.conf b/appointment-frontend/nginx.conf new file mode 100644 index 000000000..3cfe3b885 --- /dev/null +++ b/appointment-frontend/nginx.conf @@ -0,0 +1,81 @@ +# nginx.conf +worker_processes auto; +error_log /var/log/nginx/error.log; + +pid /tmp/nginx.pid; + + +events { + worker_connections 4096; +} + +http { + include /etc/nginx/mime.types; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + default_type application/octet-stream; + server_tokens off; + underscores_in_headers on; + + # Use a w3c standard log format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + server { + + # Enable HTTP Strict Transport Security (HSTS) to force clients to always + # connect via HTTPS (do not use if only testing) + add_header Strict-Transport-Security "max-age=31536000;"; + + # Enable cross-site filter (XSS) and tell browser to block detected attacks + add_header X-XSS-Protection "1; mode=block"; + + # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type + add_header X-Content-Type-Options "nosniff"; + + # Disallow the site to be rendered within a frame (clickjacking protection) + add_header X-Frame-Options "DENY"; + + # Turn off all caddy caching + add_header Cache-Control "no-cache,no-store,must-revalidate"; + add_header Pragma "no-cache"; + + # Content Security Policy + add_header Content-Security-Policy "default-src 'none';frame-src 'self' *.gov.bc.ca; script-src 'self' 'sha256-YaRF5VNtISs/hr8ATuoP3elKspUwWe/m1uAve9Sbxuk=' 'sha256-jz1UoDQhFYj7qWX/RHHnCdXPMP5++pxLOljIpiaXsPE=' *.gov.bc.ca https://maps.googleapis.com; style-src 'self' 'unsafe-inline'; font-src 'self' *.gov.bc.ca; img-src 'self' *.gov.bc.ca data: https://maps.googleapis.com ; connect-src 'self' *.gov.bc.ca; manifest-src 'self';"; + + + listen 8080; + server_name _; + + index index.html; + error_log /dev/stdout info; + access_log /dev/stdout; + + location / { + root /app; + index index.html; + try_files $uri $uri/ /index.html; + } + + # For status of ngnix service, OpenShift is configured to call this + location /nginx_status { + # Enable Nginx stats + stub_status on; + + # Only allow access from localhost + allow all; + + # Other request should be denied + # deny all; + + # No need to log this request, its just noise + access_log off; + } + } +} diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 000000000..e0a57b287 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,2 @@ +**/node_modules +**/dist diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 000000000..8a2d887af --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,15 @@ +FROM docker-remote.artifacts.developer.gov.bc.ca/node:10 as build-stage +ENV NODE_ENV=prod +ENV VUE_APP_PATH=/ +WORKDIR /app +COPY ./package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM docker-remote.artifacts.developer.gov.bc.ca/nginx:1.18.0 as production-stage +COPY nginx.conf /etc/nginx/nginx.conf +RUN mkdir /app +COPY --from=build-stage /app/dist /app +EXPOSE 8080:8080 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 000000000..571387386 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,77 @@ +# nginx.conf +worker_processes auto; +error_log /var/log/nginx/error.log; + +pid /tmp/nginx.pid; + + +events { + worker_connections 4096; +} + +http { + include /etc/nginx/mime.types; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + default_type application/octet-stream; + server_tokens off; + underscores_in_headers on; + + # Use a w3c standard log format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + server { + + # Enable HTTP Strict Transport Security (HSTS) to force clients to always + # connect via HTTPS (do not use if only testing) + add_header Strict-Transport-Security "max-age=31536000;"; + + # Enable cross-site filter (XSS) and tell browser to block detected attacks + add_header X-XSS-Protection "1; mode=block"; + + # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type + add_header X-Content-Type-Options "nosniff"; + + # Disallow the site to be rendered within a frame (clickjacking protection) + add_header X-Frame-Options "DENY"; + + # Turn off all caddy caching + add_header Cache-Control "no-cache,no-store,must-revalidate"; + add_header Pragma "no-cache"; + + listen 8080; + server_name _; + + index index.html; + error_log /dev/stdout info; + access_log /dev/stdout; + + location / { + root /app; + index index.html; + try_files $uri $uri/ /index.html; + } + + # For status of ngnix service, OpenShift is configured to call this + location /nginx_status { + # Enable Nginx stats + stub_status on; + + # Only allow access from localhost + allow all; + + # Other request should be denied + # deny all; + + # No need to log this request, its just noise + access_log off; + } + } +} diff --git a/openshift/templates/appointment-nginx-frontend-build.yaml b/openshift/templates/appointment-nginx-frontend-build.yaml new file mode 100644 index 000000000..c69f609f8 --- /dev/null +++ b/openshift/templates/appointment-nginx-frontend-build.yaml @@ -0,0 +1,109 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + labels: + app: ${NAME} + name: ${NAME}-build + annotations: + description: "" + tags: appointment,python + iconClass: icon-python +objects: + - apiVersion: v1 + kind: ImageStream + metadata: + name: ${NAME} + labels: + app: ${NAME} + - apiVersion: v1 + kind: BuildConfig + metadata: + name: ${NAME} + labels: + app: ${NAME} + spec: + output: + to: + kind: ImageStreamTag + name: ${NAME}:${OUTPUT_IMAGE_TAG} + resources: + limits: + cpu: ${CPU_LIMIT} + memory: ${MEMORY_LIMIT} + requests: + cpu: ${CPU_REQUEST} + memory: ${MEMORY_REQUEST} + runPolicy: Serial + source: + contextDir: ${SOURCE_CONTEXT_DIR} + git: + ref: ${GIT_REF} + uri: ${GIT_REPO_URL} + type: Git + strategy: + type: Docker + dockerStrategy: + "dockerfilePath": "${DOCKER_FILE_PATH}" + pullSecret: + name: artifactory-creds + postCommit: { } + nodeSelector: + successfulBuildsHistoryLimit: 10 + failedBuildsHistoryLimit: 2 + triggers: + - type: ConfigChange +parameters: + - description: | + The name assigned to all of the objects defined in this template. + You should keep this as default unless your know what your doing. + displayName: Name + name: NAME + required: true + value: appointment-nginx-frontend + - description: | + The URL to your GIT repo, don't use the this default unless + your just experimenting. + displayName: Git Repo URL + name: GIT_REPO_URL + required: true + value: https://github.com/bcgov/queue-management + - description: The git reference or branch. + displayName: Git Reference + name: GIT_REF + required: true + value: master + - description: The source context directory. + displayName: Source Context Directory + name: SOURCE_CONTEXT_DIR + required: false + value: appointment-frontend + - description: The tag given to the built image. + displayName: Output Image Tag + name: OUTPUT_IMAGE_TAG + required: true + value: latest + - description: The resources CPU limit (in cores) for this build. + displayName: Resources CPU Limit + name: CPU_LIMIT + required: true + value: "2" + - description: The resources Memory limit (in Mi, Gi, etc) for this build. + displayName: Resources Memory Limit + name: MEMORY_LIMIT + required: true + value: 4Gi + - description: The resources CPU request (in cores) for this build. + displayName: Resources CPU Request + name: CPU_REQUEST + required: true + value: "1" + - description: The resources Memory request (in Mi, Gi, etc) for this build. + displayName: Resources Memory Request + name: MEMORY_REQUEST + required: true + value: 4Gi + - description: The path and file of the docker file defining the build. + displayName: DockferFile + name: DOCKER_FILE_PATH + required: true + value: Dockerfile diff --git a/openshift/templates/appointment-nginx-frontend-dc.yaml b/openshift/templates/appointment-nginx-frontend-dc.yaml new file mode 100644 index 000000000..2e7ab72be --- /dev/null +++ b/openshift/templates/appointment-nginx-frontend-dc.yaml @@ -0,0 +1,240 @@ +apiVersion: v1 +kind: Template +labels: + template: appointment-frontend +metadata: + name: appointment-frontend +objects: + - apiVersion: v1 + kind: Service + metadata: + name: "${NAME}" + labels: + app: "${NAME}" + spec: + ports: + - name: web + port: 8080 + targetPort: 8080 + selector: + app: "${NAME}" + - apiVersion: v1 + kind: Route + metadata: + name: "${NAME}" + spec: + host: "${APPLICATION_DOMAIN}" + to: + kind: Service + name: "${NAME}" + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + - apiVersion: v1 + kind: ConfigMap + metadata: + name: "appointment-configuration.json" + labels: + app: "configuration.json" + data: + configuration.json: |- + { + "KEYCLOAK_CONFIG_URL": "./public/config/kc/keycloak-public.json", + "VUE_APP_ROOT_API": "${VUE_APP_ROOT_API}", + "hideBCServicesCard": ${HIDEBCSERVICESCARD}, + "BCEIDRegistrationUrl": "${BCEID_REG_URL}", + "VUE_APP_GOOGLE_STATIC_MAP_API_KEY": "${VUE_APP_GOOGLE_STATIC_MAP_API_KEY}", + "disableSms": ${DISABLE_SMS} + } + - apiVersion: v1 + kind: ConfigMap + metadata: + name: "keycloak-public.json" + labels: + app: "keycloak-public.json" + data: + keycloak-public.json: |- + { + "realm": "${KEYCLOAK_REALM}", + "auth-server-url": "${AUTH_SERVER_URL}", + "ssl-required": "external", + "resource": "${KEYCLOAK_RESOURCE}", + "public-client": "true", + "confidential-port": 0 + } + - apiVersion: v1 + kind: DeploymentConfig + metadata: + annotations: + description: "Defines how to deploy the application server" + name: "${NAME}" + spec: + replicas: 1 + selector: + name: ${NAME} + strategy: + type: Rolling + rollingParams: + updatePeriodSeconds: 1 + intervalSeconds: 1 + timeoutSeconds: 60 + maxSurge: 2 + maxUnavailable: 0 + resources: {} + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: "${NAME}" + name: "${NAME}" + spec: + containers: + - name: "${NAME}" + image: " " + ports: + - containerPort: 8080 + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 3 + volumeMounts: + - mountPath: /app/config/kc + name: keycloak + - mountPath: /app/config + name: appointment-configuration + terminationMessagePath: "/dev/termination-log" + terminationMessagePolicy: File + imagePullPolicy: Always + resources: + requests: + cpu: 50m + memory: 256Mi + limits: + cpu: 100m + memory: 512Mi + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst + securityContext: {} + schedulerName: default-scheduler + volumes: + - configMap: + defaultMode: 420 + name: keycloak-public.json + name: keycloak + - configMap: + defaultMode: 420 + name: appointment-configuration.json + name: appointment-configuration + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: name + operator: In + values: + - "${NAME}" + topologyKey: kubernetes.io/hostname + triggers: + - type: ConfigChange + - type: ImageChange + imageChangeParams: + automatic: true + containerNames: + - "${NAME}" + from: + kind: ImageStreamTag + namespace: ${TOOLS_WORKSPACE} + name: "${NAME}:${IMAGE_TAG}" + - apiVersion: v1 + kind: Route + metadata: + name: "${API_NAME}-rate-limited" + annotations: + # https://docs.openshift.com/container-platform/3.11/architecture/networking/routes.html + # https://github.com/openshift/router/blob/master/images/router/haproxy/conf/haproxy-config.template + # To deviate from default HAProxy values, enable below commented out line + # Values must be enclosed ins trings for HAproxy annotations + haproxy.router.openshift.io/rate-limit-connections: 'true' + # Below line allows 20 HTTP requests per IP per 30 sceonds. (Interval is always 30 seconds.) + # haproxy.router.openshift.io/rate-limit-connections.rate-http: '20' + spec: + host: "${RATE_LIMITED_API_HOSTNAME}" + path: /api + to: + kind: Service + name: "${API_NAME}" + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge +parameters: + - name: NAME + displayName: Name + description: The suffix for all created objects + required: false + value: appointment-nginx-frontend + - name: APPLICATION_DOMAIN + description: "The exposed hostname that will route to the Vue application" + - name: API_NAME + displayName: API Name + description: The name of the API that this uses as a backend (to create a rate limited route). + required: false + value: queue-management-api + - name: RATE_LIMITED_API_HOSTNAME + description: "The new route to API which will be rate limited" + - name: IMAGE_TAG + description: "The image tag to pull for the deployment." + required: true + value: latest + - name: TOOLS_WORKSPACE + description: The Location for your image streams (Tools Workspace) + required: true + - name: VUE_APP_ROOT_API + description: "The new route to API which will be rate limited" + value: "" + - name: BCEID_REG_URL + description: The BCEID Registration URL. + required: true + value: "" + - name: VUE_APP_GOOGLE_STATIC_MAP_API_KEY + description: API KEY for Google Map + required: true + value: "" + - name: KEYCLOAK_REALM + description: "Keycloak Realm" + required: true + value: "" + - name: KEYCLOAK_RESOURCE + description: "Keycloak client id" + required: true + value: "" + - name: KEYCLOAK_SECRET + description: "Keycloak Secret" + required: true + - name: HIDEBCSERVICESCARD + description: "this is to display/hide BCServices card authentication option" + required: true + value: "" + - name: AUTH_SERVER_URL + description: "Keycloak authorization url for ex:https://keycloakserver.com/auth/" + required: true + value: "" + - name: DISABLE_SMS + description: "Flag to decide if SMS is disabled" + required: true + value: "true" + + diff --git a/openshift/templates/queue-management-nginx-frontend-build.yaml b/openshift/templates/queue-management-nginx-frontend-build.yaml new file mode 100644 index 000000000..f5c3942fb --- /dev/null +++ b/openshift/templates/queue-management-nginx-frontend-build.yaml @@ -0,0 +1,109 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + labels: + app: ${NAME} + name: ${NAME}-build + annotations: + description: "" + tags: queue-management-frontend,queue-management,python + iconClass: icon-python +objects: + - apiVersion: v1 + kind: ImageStream + metadata: + name: ${NAME} + labels: + app: ${NAME} + - apiVersion: v1 + kind: BuildConfig + metadata: + name: ${NAME} + labels: + app: ${NAME} + spec: + output: + to: + kind: ImageStreamTag + name: ${NAME}:${OUTPUT_IMAGE_TAG} + resources: + limits: + cpu: ${CPU_LIMIT} + memory: ${MEMORY_LIMIT} + requests: + cpu: ${CPU_REQUEST} + memory: ${MEMORY_REQUEST} + runPolicy: Serial + source: + contextDir: ${SOURCE_CONTEXT_DIR} + git: + ref: ${GIT_REF} + uri: ${GIT_REPO_URL} + type: Git + strategy: + type: Docker + dockerStrategy: + "dockerfilePath": "${DOCKER_FILE_PATH}" + pullSecret: + name: artifactory-creds + postCommit: { } + nodeSelector: + successfulBuildsHistoryLimit: 10 + failedBuildsHistoryLimit: 2 + triggers: + - type: ConfigChange +parameters: + - description: | + The name assigned to all of the objects defined in this template. + You should keep this as default unless your know what your doing. + displayName: Name + name: NAME + required: true + value: queue-management-nginx-frontend + - description: | + The URL to your GIT repo, don't use the this default unless + your just experimenting. + displayName: Git Repo URL + name: GIT_REPO_URL + required: true + value: https://github.com/bcgov/queue-management + - description: The git reference or branch. + displayName: Git Reference + name: GIT_REF + required: true + value: master + - description: The source context directory. + displayName: Source Context Directory + name: SOURCE_CONTEXT_DIR + required: false + value: frontend + - description: The tag given to the built image. + displayName: Output Image Tag + name: OUTPUT_IMAGE_TAG + required: true + value: latest + - description: The resources CPU limit (in cores) for this build. + displayName: Resources CPU Limit + name: CPU_LIMIT + required: true + value: "2" + - description: The resources Memory limit (in Mi, Gi, etc) for this build. + displayName: Resources Memory Limit + name: MEMORY_LIMIT + required: true + value: 4Gi + - description: The resources CPU request (in cores) for this build. + displayName: Resources CPU Request + name: CPU_REQUEST + required: true + value: "1" + - description: The resources Memory request (in Mi, Gi, etc) for this build. + displayName: Resources Memory Request + name: MEMORY_REQUEST + required: true + value: 4Gi + - description: The path and file of the docker file defining the build. + displayName: DockferFile + name: DOCKER_FILE_PATH + required: true + value: Dockerfile diff --git a/openshift/templates/queue-management-nginx-frontend-dc.yml b/openshift/templates/queue-management-nginx-frontend-dc.yml new file mode 100644 index 000000000..46c962613 --- /dev/null +++ b/openshift/templates/queue-management-nginx-frontend-dc.yml @@ -0,0 +1,183 @@ +apiVersion: v1 +kind: Template +labels: + template: queue-management-nginx-frontend +metadata: + name: queue-management-nginx-frontend +objects: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: frontend-videos + spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: "1Gi" + - apiVersion: v1 + kind: ConfigMap + metadata: + name: keycloak + labels: + app: "${NAME}" + data: + keycloak.json: | + { + "realm": "${REALM}", + "auth-server-url": "${AUTH_SERVER_URL}", + "ssl-required": "external", + "resource": "${RESOURCE}", + "public-client": "true", + "confidential-port": 0 + } + - apiVersion: v1 + kind: Service + metadata: + name: "${NAME}" + labels: + app: "${NAME}" + spec: + ports: + - name: web + port: 8080 + targetPort: 8080 + selector: + app: "${NAME}" + - apiVersion: v1 + kind: Route + metadata: + name: "${NAME}" + spec: + host: "${APPLICATION_DOMAIN}" + to: + kind: Service + name: "${NAME}" + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + - apiVersion: v1 + kind: DeploymentConfig + metadata: + annotations: + description: "Defines how to deploy the application server" + name: "${NAME}" + spec: + replicas: 2 + selector: + name: ${NAME} + strategy: + type: Rolling + rollingParams: + updatePeriodSeconds: 1 + intervalSeconds: 1 + timeoutSeconds: 60 + maxSurge: 2 + maxUnavailable: 0 + resources: {} + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: "${NAME}" + name: "${NAME}" + spec: + containers: + - name: "${NAME}" + image: " " + ports: + - containerPort: 8080 + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 3 + livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 3 + periodSeconds: 10 + successThreshold: 1 + tcpSocket: + port: 8080 + timeoutSeconds: 3 + volumeMounts: + - mountPath: /app/static/keycloak + name: keycloak + - mountPath: /app/static/videos + name: frontend-videos + terminationMessagePath: "/dev/termination-log" + terminationMessagePolicy: File + imagePullPolicy: Always + resources: + requests: + cpu: 50m + memory: 256Mi + limits: + cpu: 100m + memory: 512Mi + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst + securityContext: {} + schedulerName: default-scheduler + volumes: + - configMap: + defaultMode: 420 + name: keycloak + name: keycloak + - name: frontend-videos + persistentVolumeClaim: + claimName: frontend-videos + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: name + operator: In + values: + - "${NAME}" + topologyKey: kubernetes.io/hostname + triggers: + - type: ConfigChange + - type: ImageChange + imageChangeParams: + automatic: true + containerNames: + - "${NAME}" + from: + kind: ImageStreamTag + namespace: ${TOOLS_WORKSPACE} + name: "${NAME}:${IMAGE_TAG}" +parameters: + - name: NAME + displayName: Name + description: The suffix for all created objects + required: false + value: queue-management-nginx-frontend + - name: APPLICATION_DOMAIN + description: "The exposed hostname that will route to the Vue application" + value: "" + - name: IMAGE_TAG + description: The image tag to pull for the deployment. + required: true + value: dev + - name: TOOLS_WORKSPACE + description: The Location for your image streams (Tools Workspace) + required: true + - name: REALM + description: "Keycloak realm" + required: true + - name: RESOURCE + description: "keycloak client id" + required: true + - name: AUTH_SERVER_URL + description: "keycloak server URL. Ex:https://yourkeycloakserver.com/auth/" + required: true + + + +