├── .gitignore ├── README.md ├── architecture.png ├── container_native.gliffy ├── crunchy ├── conf │ ├── apiserver │ │ ├── pgo.load-template.json │ │ ├── pgo.lspvc-template.json │ │ ├── pgo.yaml │ │ └── pgouser │ └── postgres-operator │ │ ├── README.txt │ │ ├── backup-job.json │ │ ├── cluster │ │ └── 1 │ │ │ ├── affinity.json │ │ │ ├── cluster-deployment-1.json │ │ │ ├── cluster-replica-deployment-1-shared.json │ │ │ ├── cluster-replica-deployment-1.json │ │ │ ├── cluster-service-1.json │ │ │ ├── cluster-upgrade-job-1.json │ │ │ └── collect.json │ │ ├── pvc-storageclass.json │ │ ├── pvc.json │ │ └── rmdata-job.json ├── crunchy-pvc.yaml ├── crunchy-template.yaml ├── db │ └── db.sql ├── deploy-HA-db.md └── deploy-cruncy.md ├── istio ├── README.md ├── addons.md ├── enabling-tls.md ├── enabling_sso.md └── istio-sso.md ├── media ├── step1.png ├── step10.png ├── step11.png ├── step12.png ├── step13.png ├── step14.png ├── step2.png ├── step3.png ├── step4.png ├── step5.png ├── step6.png ├── step7.png ├── step8.png └── step9.png ├── microsegmentation └── README.md ├── openshift └── README.md ├── spring ├── README.md ├── accessing_pg.md ├── adding_istio_sidecar.md ├── product-catalog │ ├── README.adoc │ ├── pom.xml │ └── src │ │ ├── istio │ │ ├── istio-product-catalog-0.0.1-all.yml │ │ ├── mixer-rule-only-authorized.yaml │ │ └── product-catalog-auth_config.yaml │ │ ├── main │ │ ├── fabric8 │ │ │ ├── deployment.yml │ │ │ ├── deploymentconfig.yml │ │ │ └── service.yml │ │ ├── java │ │ │ └── com │ │ │ │ └── redhat │ │ │ │ └── productcatalog │ │ │ │ ├── ProductCatalogApplication.java │ │ │ │ ├── controllers │ │ │ │ └── ProductCatalogController.java │ │ │ │ ├── entities │ │ │ │ └── Product.java │ │ │ │ ├── interfaces │ │ │ │ └── ProductCatalog.java │ │ │ │ ├── repositories │ │ │ │ └── ProductRepository.java │ │ │ │ └── services │ │ │ │ └── ProductCatalogService.java │ │ └── resources │ │ │ ├── application-openshift.properties │ │ │ ├── application.properties │ │ │ ├── bootstrap-openshift.yml │ │ │ └── static │ │ │ └── index.html │ │ └── test │ │ └── java │ │ └── com │ │ └── redhat │ │ └── productcatalog │ │ └── ProductCatalogServiceTests.java ├── product-frontend │ ├── .gitignore │ ├── README.adoc │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── fabric8 │ │ │ ├── deployment.yml │ │ │ └── route.yml │ │ ├── java │ │ │ └── com │ │ │ │ └── redhat │ │ │ │ └── productfrontend │ │ │ │ └── ProductFrontendApplication.java │ │ └── resources │ │ │ ├── application-openshift.properties │ │ │ ├── application.properties │ │ │ └── static │ │ │ ├── index.html │ │ │ ├── js │ │ │ └── keycloak-3.1.0.min.js │ │ │ └── keycloak.json │ │ └── test │ │ └── java │ │ └── com │ │ └── redhat │ │ └── productfrontend │ │ └── ProductFrontendApplicationTests.java └── product-inventory │ ├── .gitignore │ ├── README.adoc │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── fabric8 │ │ ├── configmap.yml │ │ ├── deploymentconfig.yml │ │ ├── route.yml │ │ └── service.yml │ ├── java │ │ └── com │ │ │ └── redhat │ │ │ └── productinventory │ │ │ ├── HttpHeaderForwarderClientHttpRequestInterceptor.java │ │ │ ├── HttpHeaderForwarderHandlerInterceptor.java │ │ │ ├── ProductInventoryApplication.java │ │ │ ├── controllers │ │ │ └── ProductInventoryController.java │ │ │ ├── dtos │ │ │ └── InventoryDTO.java │ │ │ ├── entities │ │ │ └── Inventory.java │ │ │ ├── interfaces │ │ │ └── ProductInventory.java │ │ │ ├── repositories │ │ │ └── InventoryRepository.java │ │ │ └── services │ │ │ └── ProductInventoryService.java │ └── resources │ │ ├── application-openshift.properties │ │ ├── application.properties │ │ └── bootstrap-openshift.yml │ └── test │ └── java │ └── com │ └── redhat │ └── productinventory │ └── ProductInventoryApplicationTests.java ├── sso ├── README.md ├── inventoryservice-client.json └── service.sso.yaml └── vault ├── .gitignore ├── app-policy.hcl ├── deploy-vault.md ├── vault-config.json ├── vault-kube-backend.md ├── vault-postgres.md └── vault.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | target/ 3 | out/ 4 | .DS_Store 5 | *.classpath 6 | *.project 7 | *.settings 8 | .security/ 9 | *.jar 10 | *.war 11 | *.iml 12 | .idea 13 | workspace 14 | data 15 | logs/ 16 | tmp/ 17 | .vscode 18 | .mvn 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container Native Microservices with OpenShift Sample Application 2 | 3 | This tutorial shows the steps to install and run a Spring Boot microservice with PostgreSQL in a container-native way. 4 | 5 | This sample application is comprised of: 6 | 7 | * Spring Boot 1.5 with RHOAR for microservices *(In Progress)* 8 | * Red Hat SSO 7.1 (a.k.a. Keycloak 2.5.5) for user management, authentication with JWT 9 | * Scalable, High Availability configuration using Kube Ping 10 | * Persistence with PostgreSQL 11 | * Pre-configured Realms for quick setup *(TODO)* 12 | * Istio 0.4 (latest) for service mesh and security *(In Progress)* 13 | * Injection of Envoy/Istio sidecar proxy into microservice pod 14 | * Authorization of web service access via JWT and SSO with Istio Mixer rule 15 | * Mutual TLS 16 | * Microsegmentation Rules (TODO) 17 | * Prometheus (Istio Integration) 18 | * Zipkin (Istio Integration) 19 | * Grafana (Istio Integration) 20 | * Service Graph (Istio Integration) 21 | * Hashicorp Vault for managing secrets *(In Progress)* 22 | * Crunchy Operator for High Availability PostgreSQL *(In Progress)* 23 | 24 | The application has the following architecture: 25 | 26 | ![Architecture](architecture.png) 27 | 28 | ## Assumptions: 29 | 1. You have an OpenShift Container Platform cluster >= 3.7 30 | 2. You have dynamic volume provisioning available (it's possible to use static provisioning provided that you have enough volumes) 31 | 3. You are running the network policy plugin. 32 | * To enable the network policy plugin on Minishift use: 33 | ``` ./minishift openshift config set --patch='{"networkConfig":{"networkPluginName":"redhat/openshift-ovs-networkpolicy"}} ``` 34 | 35 | 36 | Here are the steps for the installation: 37 | 38 | | Step | Architcture 39 | |:-:|:-:| 40 | | 1. [Deploy the Crunchy Postgres operator](./crunchy/deploy-cruncy.md) | ![step1](./media/step1.png) | 41 | | 2. [Deploy Postgres in HA](./crunchy/deploy-HA-db.md) | ![step2](./media/step2.png) | 42 | | 3. [Deploy Hashicorp Vault](./vault/deploy-vault.md) | ![step3](./media/step3.png) | 43 | | 4. [Configure Vault to use Kubernetes backend authentication](./vault/vault-kube-backend.md) | ![step4](./media/step4.png) | 44 | | 5. [Configure Vault to manage the postgresql DB](./vault/vault-postgres.md) | ![step5](./media/step5.png) | 45 | | 6. [Configure application to use Vault to retrieve the postgresql account](./spring/accessing_pg.md) | ![step6](./media/step6.png) | 46 | | 7. [RH SSO installation](./sso/README.md) | ![step7](./media/step7.png) | 47 | | 8. [Istio core installation](./istio/README.md) | ![step8](./media/step8.png) | 48 | | 9. [Configure app to use Istio](./spring/adding_istio_sidecar.md) | ![step9](./media/step9.png) | 49 | | 10. [Configure Istio to do Mutual TLS authentication](./istio/enabling-tls.md) | ![step10](./media/step10.png) | 50 | | 11. [Configure istio to do OAuth Authentication via RH SSO](./istio/enabling_sso.md) | ![step11](./media/step11.png) | 51 | | 12. [Istio Add-ons installation (prometheus, Jaeger)](./istio/addons.md) | ![step12](./media/step12.png) | 52 | | 13. [Configure microsegmentation](./microsegmentation/README.md) | ![step13](./media/step13.png) | 53 | | 14. [Build and deploy the application](./spring/README.md) | ![step14](./media/step14.png) | 54 | 55 | 56 | # POC Outcome (as of 2/1/2018) 57 | 58 | | technology | production ready | comments | 59 | |:-:|:-:|:-:| 60 | | RHOAR | yes | the maven plugin may not be fitting for all use cases | 61 | | HA database running in openshift | yes | - HA templates to be developed with the customer - day 2 operations still not container native | 62 | | postgres operator | no | main reason: lack of integration with enterprise security, it's in the roadmap to solve this | 63 | | Vault | yes | | 64 | | RH SSO | yes | | 65 | | istio | no | privileged scc permission needs to be given to all pods running in the mesh | 66 | | Application managed OAuth | yes | | 67 | | Istio managed OAuth | no | seems a very frail configuration at this point, future versions of Istio may solve this issue | 68 | | Istio managed mTLS | yes | more testing required | 69 | | microsegmentation | N/A | not completed at this point | 70 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/architecture.png -------------------------------------------------------------------------------- /crunchy/conf/apiserver/pgo.load-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "batch/v1", 3 | "kind": "Job", 4 | "metadata": { 5 | "name": "{{.Name}}" 6 | }, 7 | "spec": { 8 | "template": { 9 | "metadata": { 10 | "name": "{{.Name}}", 11 | "labels": { 12 | "pgo-load": "true", 13 | "pg-database": "{{.DbHost}}" 14 | } 15 | }, 16 | "spec": { 17 | "volumes": [{ 18 | "name": "pgdata", 19 | "persistentVolumeClaim" : { 20 | "claimName": "{{.PVCName}}" 21 | } 22 | }], 23 | 24 | {{.SecurityContext}} 25 | 26 | "containers": [{ 27 | "name": "csvload", 28 | "image": "{{.COImagePrefix}}/pgo-load:{{.COImageTag}}", 29 | "volumeMounts": [{ 30 | "mountPath": "/pgdata", 31 | "name": "pgdata", 32 | "readOnly": false 33 | }], 34 | "env": [{ 35 | "name": "TABLE_TO_LOAD", 36 | "value": "{{.TableToLoad}}" 37 | }, { 38 | "name": "FILE_PATH", 39 | "value": "{{.FilePath}}" 40 | }, { 41 | "name": "FILE_TYPE", 42 | "value": "{{.FileType}}" 43 | }, { 44 | "name": "DB_HOST", 45 | "value": "{{.DbHost}}" 46 | }, { 47 | "name": "DB_DATABASE", 48 | "value": "{{.DbDatabase}}" 49 | }, { 50 | "name": "DB_USER", 51 | "value": "{{.DbUser}}" 52 | }, { 53 | "name": "DB_PASS", 54 | "value": "{{.DbPass}}" 55 | }, { 56 | "name": "DB_PORT", 57 | "value": "{{.DbPort}}" 58 | }] 59 | }], 60 | "restartPolicy": "Never" 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crunchy/conf/apiserver/pgo.lspvc-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Pod", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "{{.Name}}", 6 | "labels": { 7 | "name": "lspvc", 8 | "pvcname": "{{.PVCName}}" 9 | } 10 | }, 11 | "spec": { 12 | "restartPolicy": "Never", 13 | "containers": [{ 14 | "name": "lspvc", 15 | "securityContext": { 16 | "privileged": false 17 | }, 18 | "image": "{{.COImagePrefix}}/pgo-lspvc:{{.COImageTag}}", 19 | "env": [{ 20 | "name": "BACKUP_ROOT", 21 | "value": "{{.BackupRoot}}" 22 | }], 23 | "volumeMounts": [{ 24 | "mountPath": "/pgdata", 25 | "name": "pgdata", 26 | "readOnly": true 27 | }] 28 | }], 29 | "volumes": [{ 30 | "name": "pgdata", 31 | "persistentVolumeClaim": { 32 | "claimName": "{{.PVCName}}" 33 | } 34 | }] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crunchy/conf/apiserver/pgo.yaml: -------------------------------------------------------------------------------- 1 | Namespace: pgo 2 | Cluster: 3 | CCPImagePrefix: crunchydata 4 | CCPImageTag: centos7-10.1-1.7.0 5 | Port: 5432 6 | PrimaryPassword: password 7 | User: testuser 8 | Password: admin 9 | RootPassword: admin 10 | Database: userdb 11 | PasswordAgeDays: 60 12 | PasswordLength: 8 13 | Strategy: 1 14 | Replicas: 1 15 | PrimaryStorage: 16 | AccessMode: ReadWriteMany 17 | Size: 200M 18 | StorageType: create 19 | BackupStorage: 20 | AccessMode: ReadWriteMany 21 | Size: 200M 22 | StorageType: create 23 | ReplicaStorage: 24 | AccessMode: ReadWriteMany 25 | Size: 200M 26 | StorageType: create 27 | Pgo: 28 | LSPVCTemplate: /config/pgo.lspvc-template.json 29 | LoadTemplate: /config/pgo.load-template.json 30 | COImagePrefix: crunchydata 31 | COImageTag: centos7-2.4 -------------------------------------------------------------------------------- /crunchy/conf/apiserver/pgouser: -------------------------------------------------------------------------------- 1 | username:password 2 | pgoadmin:pgoadmin -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/README.txt: -------------------------------------------------------------------------------- 1 | JSON templates are stored in this directory, the postgres-operator 2 | will read these templates and use them for creating various Kube kinds 3 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/backup-job.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "batch/v1", 3 | "kind": "Job", 4 | "metadata": { 5 | "name": "backup-{{.Name}}" 6 | }, 7 | "spec": { 8 | "template": { 9 | "metadata": { 10 | "name": "{{.Name}}", 11 | "labels": { 12 | "pgbackup": "true", 13 | "pg-database": "{{.Name}}" 14 | } 15 | }, 16 | "spec": { 17 | "volumes": [{ 18 | "name": "pgdata", 19 | {{.PvcName}} 20 | }], 21 | 22 | {{.SecurityContext}} 23 | 24 | "containers": [{ 25 | "name": "backup", 26 | "image": "{{.CCPImagePrefix}}/crunchy-backup:{{.CCPImageTag}}", 27 | "volumeMounts": [{ 28 | "mountPath": "/pgdata", 29 | "name": "pgdata", 30 | "readOnly": false 31 | }], 32 | "env": [{ 33 | "name": "BACKUP_HOST", 34 | "value": "{{.BackupHost}}" 35 | }, { 36 | "name": "BACKUP_USER", 37 | "value": "{{.BackupUser}}" 38 | }, { 39 | "name": "BACKUP_PASS", 40 | "value": "{{.BackupPass}}" 41 | }, { 42 | "name": "BACKUP_PORT", 43 | "value": "{{.BackupPort}}" 44 | }] 45 | }], 46 | "restartPolicy": "Never" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/affinity.json: -------------------------------------------------------------------------------- 1 | "affinity": { 2 | "nodeAffinity": { 3 | "preferredDuringSchedulingIgnoredDuringExecution": [{ 4 | "weight": 1, 5 | "preference": { 6 | "matchExpressions": [{ 7 | "key": "kubernetes.io/hostname", 8 | "operator": "{{.Operator}}", 9 | "values": [ 10 | "{{.Node}}" 11 | ] 12 | }] 13 | } 14 | }] 15 | } 16 | }, 17 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/cluster-deployment-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Deployment", 3 | "apiVersion": "extensions/v1beta1", 4 | "metadata": { 5 | "name": "{{.Name}}", 6 | "labels": { 7 | {{.OperatorLabels }} 8 | } 9 | }, 10 | "spec": { 11 | "replicas": 1, 12 | "template": { 13 | "metadata": { 14 | "labels": { 15 | "name": "{{.Name}}", 16 | "primary": "true", 17 | "pg-cluster": "{{.ClusterName}}" 18 | } 19 | }, 20 | "spec": { 21 | 22 | {{.SecurityContext }} 23 | 24 | "containers": [ 25 | 26 | {{.CollectAddon }} 27 | 28 | { 29 | "name": "database", 30 | "image": "{{.CCPImagePrefix}}/crunchy-postgres:{{.CCPImageTag}}", 31 | "readinessProbe": { 32 | "exec": { 33 | "command": [ 34 | "/opt/cpm/bin/readiness.sh" 35 | ] 36 | }, 37 | "initialDelaySeconds": 15, 38 | "timeoutSeconds": 8 39 | }, 40 | "env": [{ 41 | "name": "PG_PRIMARY_PORT", 42 | "value": "{{.Port}}" 43 | }, { 44 | "name": "PG_MODE", 45 | "value": "primary" 46 | }, { 47 | "name": "PGDATA_PATH_OVERRIDE", 48 | "value": "{{.DataPathOverride}}" 49 | }, { 50 | "name": "BACKUP_PATH", 51 | "value": "{{.BackupPath}}" 52 | }, { 53 | "name": "PG_DATABASE", 54 | "value": "{{.Database}}" 55 | }, { 56 | "name": "PGHOST", 57 | "value": "/tmp" 58 | }], 59 | "volumeMounts": [{ 60 | "mountPath": "/pgdata", 61 | "name": "pgdata", 62 | "readOnly": false 63 | }, { 64 | "mountPath": "/backup", 65 | "name": "backup", 66 | "readOnly": true 67 | }, { 68 | "mountPath": "/pguser", 69 | "name": "user-volume" 70 | }, { 71 | "mountPath": "/pgprimary", 72 | "name": "primary-volume" 73 | }, { 74 | "mountPath": "/pgroot", 75 | "name": "root-volume" 76 | }, { 77 | "mountPath": "/pgwal", 78 | "name": "pgwal-volume" 79 | }, { 80 | "mountPath": "/pgconf", 81 | "name": "pgconf-volume" 82 | }, { 83 | "mountPath": "/recover", 84 | "name": "recover-volume" 85 | }, { 86 | "mountPath": "/backrestrepo", 87 | "name": "backrestrepo-volume" 88 | } 89 | 90 | ], 91 | 92 | "ports": [{ 93 | "containerPort": 5432, 94 | "protocol": "TCP" 95 | }], 96 | "resources": {}, 97 | "imagePullPolicy": "IfNotPresent" 98 | }], 99 | "volumes": [{ 100 | "name": "pgdata", 101 | {{.PVCName}} 102 | }, { 103 | "name": "backup", 104 | {{.BackupPVCName}} 105 | }, { 106 | "name": "user-volume", 107 | "secret": { 108 | "secretName": "{{.UserSecretName}}" 109 | } 110 | }, { 111 | "name": "primary-volume", 112 | "secret": { 113 | "secretName": "{{.PrimarySecretName}}" 114 | } 115 | }, { 116 | "name": "root-volume", 117 | "secret": { 118 | "secretName": "{{.RootSecretName}}" 119 | } 120 | }, { 121 | "name": "pgwal-volume", 122 | "emptyDir": { "medium": "Memory" } 123 | }, { 124 | "name": "backrestrepo-volume", 125 | "emptyDir": { "medium": "Memory" } 126 | }, { 127 | "name": "recover-volume", 128 | "emptyDir": { "medium": "Memory" } 129 | }, { 130 | "name": "pgconf-volume", 131 | "emptyDir": { "medium": "Memory" } 132 | } 133 | 134 | ], 135 | 136 | {{.NodeSelector}} 137 | 138 | "restartPolicy": "Always", 139 | "dnsPolicy": "ClusterFirst" 140 | } 141 | }, 142 | "strategy": { 143 | "type": "RollingUpdate", 144 | "rollingUpdate": { 145 | "maxUnavailable": 1, 146 | "maxSurge": 1 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/cluster-replica-deployment-1-shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Deployment", 3 | "apiVersion": "extensions/v1beta1", 4 | "metadata": { 5 | "name": "{{.Name}}" 6 | }, 7 | "spec": { 8 | "replicas": {{.Replicas}}, 9 | "template": { 10 | "metadata": { 11 | "labels": { 12 | {{.OperatorLabels}} 13 | } 14 | }, 15 | "spec": { 16 | 17 | {{.SecurityContext}} 18 | 19 | "containers": [{ 20 | "name": "database", 21 | "image": "{{.CCPImagePrefix}}/crunchy-postgres:{{.CCPImageTag}}", 22 | "env": [{ 23 | "name": "PG_PRIMARY_PORT", 24 | "value": "{{.Port}}" 25 | }, { 26 | "name": "PG_PRIMARY_HOST", 27 | "value": "{{.PrimaryHost}}" 28 | }, { 29 | "name": "PG_MODE", 30 | "value": "replica" 31 | }, { 32 | "name": "PG_DATABASE", 33 | "value": "{{.Database}}" 34 | }, { 35 | "name": "PGHOST", 36 | "value": "/tmp" 37 | }], 38 | "volumeMounts": [ 39 | { 40 | "mountPath": "/pgdata", 41 | "name": "pgdata", 42 | "readOnly": false 43 | }, { 44 | "mountPath": "/pguser", 45 | "name": "user-volume" 46 | }, { 47 | "mountPath": "/pgprimary", 48 | "name": "primary-volume" 49 | }, { 50 | "mountPath": "/pgroot", 51 | "name": "root-volume" 52 | }, { 53 | "mountPath": "/pgwal", 54 | "name": "pgwal-volume" 55 | }, { 56 | "mountPath": "/backrestrepo", 57 | "name": "backrestrepo-volume" 58 | }, { 59 | "mountPath": "/recover", 60 | "name": "recover-volume" 61 | }, { 62 | "mountPath": "/pgconf", 63 | "name": "pgconf-volume" 64 | } 65 | ], 66 | 67 | "ports": [{ 68 | "containerPort": 5432, 69 | "protocol": "TCP" 70 | }], 71 | "resources": {}, 72 | "imagePullPolicy": "IfNotPresent" 73 | }], 74 | "volumes": [ 75 | { 76 | "name": "pgdata", 77 | "persistentVolumeClaim": { 78 | "claimName": "{{.PVCName}}" 79 | } 80 | }, { 81 | "name": "user-volume", 82 | "secret": { 83 | "secretName": "{{.UserSecretName}}" 84 | } 85 | }, { 86 | "name": "primary-volume", 87 | "secret": { 88 | "secretName": "{{.PrimarySecretName}}" 89 | } 90 | }, { 91 | "name": "root-volume", 92 | "secret": { 93 | "secretName": "{{.RootSecretName}}" 94 | } 95 | }, { 96 | "name": "pgwal-volume", 97 | "emptyDir": { "medium": "Memory" } 98 | }, { 99 | "name": "backrestrepo-volume", 100 | "emptyDir": { "medium": "Memory" } 101 | }, { 102 | "name": "recover-volume", 103 | "emptyDir": { "medium": "Memory" } 104 | }, { 105 | "name": "pgconf-volume", 106 | "emptyDir": { "medium": "Memory" } 107 | } 108 | 109 | ], 110 | 111 | {{.NodeSelector}} 112 | 113 | "restartPolicy": "Always", 114 | "dnsPolicy": "ClusterFirst" 115 | } 116 | }, 117 | "strategy": { 118 | "type": "RollingUpdate", 119 | "rollingUpdate": { 120 | "maxUnavailable": 1, 121 | "maxSurge": 1 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/cluster-replica-deployment-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Deployment", 3 | "apiVersion": "extensions/v1beta1", 4 | "metadata": { 5 | "name": "{{.Name}}" 6 | }, 7 | "spec": { 8 | "replicas": {{.Replicas}}, 9 | "template": { 10 | "metadata": { 11 | "labels": { 12 | {{.OperatorLabels}} 13 | } 14 | }, 15 | "spec": { 16 | 17 | {{.SecurityContext}} 18 | 19 | "containers": [{ 20 | "name": "database", 21 | "image": "{{.CCPImagePrefix}}/crunchy-postgres:{{.CCPImageTag}}", 22 | "env": [{ 23 | "name": "PG_PRIMARY_PORT", 24 | "value": "{{.Port}}" 25 | }, { 26 | "name": "PG_PRIMARY_HOST", 27 | "value": "{{.PrimaryHost}}" 28 | }, { 29 | "name": "PG_MODE", 30 | "value": "replica" 31 | }, { 32 | "name": "PG_DATABASE", 33 | "value": "{{.Database}}" 34 | }, { 35 | "name": "PGHOST", 36 | "value": "/tmp" 37 | }], 38 | "volumeMounts": [ 39 | { 40 | "mountPath": "/pgdata", 41 | "name": "pgdata", 42 | "readOnly": false 43 | }, { 44 | "mountPath": "/pguser", 45 | "name": "user-volume" 46 | }, { 47 | "mountPath": "/pgprimary", 48 | "name": "primary-volume" 49 | }, { 50 | "mountPath": "/pgroot", 51 | "name": "root-volume" 52 | }, { 53 | "mountPath": "/pgwal", 54 | "name": "pgwal-volume" 55 | }, { 56 | "mountPath": "/pgconf", 57 | "name": "pgconf-volume" 58 | }, { 59 | "mountPath": "/recover", 60 | "name": "recover-volume" 61 | }, { 62 | "mountPath": "/backrestrepo", 63 | "name": "backrestrepo-volume" 64 | } 65 | ], 66 | 67 | "ports": [{ 68 | "containerPort": 5432, 69 | "protocol": "TCP" 70 | }], 71 | "resources": {}, 72 | "imagePullPolicy": "IfNotPresent" 73 | }], 74 | "volumes": [ 75 | { 76 | "name": "pgdata", 77 | "emptyDir": {} 78 | }, { 79 | "name": "user-volume", 80 | "secret": { 81 | "secretName": "{{.UserSecretName}}" 82 | } 83 | }, { 84 | "name": "primary-volume", 85 | "secret": { 86 | "secretName": "{{.PrimarySecretName}}" 87 | } 88 | }, { 89 | "name": "root-volume", 90 | "secret": { 91 | "secretName": "{{.RootSecretName}}" 92 | } 93 | }, { 94 | "name": "pgwal-volume", 95 | "emptyDir": { "medium": "Memory" } 96 | }, { 97 | "name": "backrestrepo-volume", 98 | "emptyDir": { "medium": "Memory" } 99 | }, { 100 | "name": "recover-volume", 101 | "emptyDir": { "medium": "Memory" } 102 | }, { 103 | "name": "pgconf-volume", 104 | "emptyDir": { "medium": "Memory" } 105 | } 106 | 107 | ], 108 | 109 | {{.NodeSelector}} 110 | 111 | "restartPolicy": "Always", 112 | "dnsPolicy": "ClusterFirst" 113 | } 114 | }, 115 | "strategy": { 116 | "type": "RollingUpdate", 117 | "rollingUpdate": { 118 | "maxUnavailable": 1, 119 | "maxSurge": 1 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/cluster-service-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Service", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "{{.Name}}", 6 | "labels": { 7 | "pg-cluster": "{{.ClusterName}}", 8 | "name": "{{.Name}}" 9 | } 10 | }, 11 | "spec": { 12 | "ports": [{ 13 | "protocol": "TCP", 14 | "port": {{.Port}}, 15 | "targetPort": {{.Port}}, 16 | "nodePort": 0 17 | }], 18 | "selector": { 19 | "name": "{{.Name}}" 20 | }, 21 | "type": "ClusterIP", 22 | "sessionAffinity": "None" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/cluster-upgrade-job-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "batch/v1", 3 | "kind": "Job", 4 | "metadata": { 5 | "name": "upgrade-{{.Name}}" 6 | }, 7 | "spec": { 8 | "template": { 9 | "metadata": { 10 | "name": "{{.Name}}", 11 | "labels": { 12 | "pgupgrade": "true", 13 | "pg-database": "{{.Name}}" 14 | } 15 | }, 16 | "spec": { 17 | "volumes": [{ 18 | "name": "pgolddata", 19 | "persistentVolumeClaim": { 20 | "claimName": "{{.OldPVCName}}" 21 | } 22 | }, { 23 | "name": "pgnewdata", 24 | "persistentVolumeClaim": { 25 | "claimName": "{{.NewPVCName}}" 26 | } 27 | }], 28 | 29 | {{.SecurityContext}} 30 | 31 | "containers": [{ 32 | "name": "upgrade", 33 | "image": "{{.CCPImagePrefix}}/crunchy-upgrade:{{.CCPImageTag}}", 34 | "volumeMounts": [{ 35 | "mountPath": "/pgolddata", 36 | "name": "pgolddata", 37 | "readOnly": false 38 | }, { 39 | "mountPath": "/pgnewdata", 40 | "name": "pgnewdata", 41 | "readOnly": false 42 | }], 43 | "env": [{ 44 | "name": "OLD_DATABASE_NAME", 45 | "value": "{{.OldDatabaseName}}" 46 | }, { 47 | "name": "NEW_DATABASE_NAME", 48 | "value": "{{.NewDatabaseName}}" 49 | }, { 50 | "name": "OLD_VERSION", 51 | "value": "{{.OldVersion}}" 52 | }, { 53 | "name": "NEW_VERSION", 54 | "value": "{{.NewVersion}}" 55 | }] 56 | }], 57 | "restartPolicy": "Never" 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/cluster/1/collect.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collect", 3 | "image": "{{.CCPImagePrefix}}/crunchy-collect:{{.CCPImageTag}}", 4 | "ports": [{ 5 | "containerPort": 9187, 6 | "protocol": "TCP" 7 | }, { 8 | "containerPort": 9100, 9 | "protocol": "TCP" 10 | }], 11 | "env": [{ 12 | "name": "PROM_GATEWAY", 13 | "value": "http://crunchy-metrics:9091" 14 | }, { 15 | "name": "DATA_SOURCE_NAME", 16 | "value": "postgresql://primaryuser:password@{{.Name}}:5432/postgres?sslmode=disable" 17 | }, { 18 | "name": "POSTGRES_EXPORTER_URL", 19 | "value": "http://localhost:9187/metrics" 20 | }, { 21 | "name": "NODE_EXPORTER_URL", 22 | "value": "http://localhost:9100/metrics" 23 | }] 24 | }, 25 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/pvc-storageclass.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeClaim", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "{{.Name}}", 6 | "labels": { 7 | "pgremove": "true" 8 | } 9 | }, 10 | "spec": { 11 | "accessModes": [ 12 | "{{.AccessMode}}" 13 | ], 14 | "storageClassName": "{{.StorageClass}}", 15 | "resources": { 16 | "requests": { 17 | "storage": "{{.Size}}" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/pvc.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeClaim", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "{{.Name}}", 6 | "labels": { 7 | "pgremove": "true" 8 | } 9 | }, 10 | "spec": { 11 | "accessModes": [ 12 | "{{.AccessMode}}" 13 | ], 14 | "resources": { 15 | "requests": { 16 | "storage": "{{.Size}}" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crunchy/conf/postgres-operator/rmdata-job.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "batch/v1", 3 | "kind": "Job", 4 | "metadata": { 5 | "name": "rmdata-{{.Name}}" 6 | }, 7 | "spec": { 8 | "template": { 9 | "metadata": { 10 | "name": "rmdata-{{.Name}}", 11 | "labels": { 12 | "pgrmdata": "true", 13 | "pg-database": "{{.Name}}", 14 | "claimName": "{{.PvcName}}" 15 | } 16 | }, 17 | "spec": { 18 | "volumes": [{ 19 | "name": "pgdata", 20 | "persistentVolumeClaim" : { 21 | "claimName": "{{.PvcName}}" 22 | } 23 | }], 24 | 25 | {{.SecurityContext}} 26 | 27 | "containers": [{ 28 | "name": "rmdata", 29 | "image": "{{.COImagePrefix}}/pgo-rmdata:{{.COImageTag}}", 30 | "volumeMounts": [{ 31 | "mountPath": "/pgdata", 32 | "name": "pgdata", 33 | "readOnly": false 34 | }], 35 | "env": [{ 36 | "name": "DATA_ROOT", 37 | "value": "{{.DataRoot}}" 38 | }] 39 | }], 40 | "restartPolicy": "Never" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crunchy/crunchy-pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: PersistentVolumeClaim 3 | apiVersion: v1 4 | metadata: 5 | name: crunchy-pvc 6 | spec: 7 | accessModes: 8 | - ReadWriteMany 9 | volumeMode: Filesystem 10 | resources: 11 | requests: 12 | storage: 400M 13 | --- 14 | kind: PersistentVolumeClaim 15 | apiVersion: v1 16 | metadata: 17 | name: csv-pvc 18 | spec: 19 | accessModes: 20 | - ReadWriteMany 21 | volumeMode: Filesystem 22 | resources: 23 | requests: 24 | storage: 100M -------------------------------------------------------------------------------- /crunchy/crunchy-template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Template 3 | metadata: 4 | name: postgres-operator-template 5 | objects: 6 | - apiVersion: v1 7 | kind: DeploymentConfig 8 | metadata: 9 | name: postgres-operator 10 | spec: 11 | replicas: 1 12 | template: 13 | metadata: 14 | labels: 15 | name: postgres-operator 16 | spec: 17 | initContainers: 18 | - name: init 19 | image: registry.access.redhat.com/rhel7-atomic 20 | command: ['/bin/bash'] 21 | args: ['-c', "cp /certs/tls.key /merged/server.key && cp /certs/tls.crt /merged/server.crt && cp -L /config/* /merged"] 22 | volumeMounts: 23 | - mountPath: /config 24 | name: apiserver-conf 25 | - name: service-certs 26 | mountPath: /certs 27 | - name: merged-conf 28 | mountPath: /merged 29 | containers: 30 | - env: 31 | - name: DEBUG 32 | value: "true" 33 | image: crunchydata/pgo-apiserver:${CO_IMAGE_TAG} 34 | imagePullPolicy: IfNotPresent 35 | name: apiserver 36 | ports: 37 | - containerPort: 8443 38 | protocol: TCP 39 | volumeMounts: 40 | - mountPath: /config 41 | name: merged-conf 42 | readOnly: true 43 | - mountPath: /operator-conf 44 | name: operator-conf 45 | readOnly: true 46 | - env: 47 | - name: CCP_IMAGE_PREFIX 48 | value: crunchydata 49 | - name: CO_IMAGE_PREFIX 50 | value: crunchydata 51 | - name: CO_IMAGE_TAG 52 | value: ${CO_IMAGE_TAG} 53 | - name: DEBUG 54 | value: "true" 55 | - name: NAMESPACE 56 | valueFrom: 57 | fieldRef: 58 | apiVersion: v1 59 | fieldPath: metadata.namespace 60 | - name: MY_POD_NAME 61 | valueFrom: 62 | fieldRef: 63 | apiVersion: v1 64 | fieldPath: metadata.name 65 | image: crunchydata/postgres-operator:${CO_IMAGE_TAG} 66 | name: operator 67 | volumeMounts: 68 | - mountPath: /operator-conf 69 | name: operator-conf 70 | readOnly: true 71 | volumes: 72 | - emptyDir: {} 73 | name: merged-conf 74 | - configMap: 75 | defaultMode: 420 76 | name: operator-conf 77 | name: operator-conf 78 | - configMap: 79 | defaultMode: 420 80 | name: apiserver-conf 81 | name: apiserver-conf 82 | - name: service-certs 83 | secret: 84 | secretName: service-certs 85 | - apiVersion: v1 86 | kind: Service 87 | metadata: 88 | name: postgres-operator 89 | annotations: 90 | service.alpha.openshift.io/serving-cert-secret-name: service-certs 91 | spec: 92 | ports: 93 | - name: apiserver 94 | port: 8443 95 | protocol: TCP 96 | targetPort: 8443 97 | selector: 98 | name: postgres-operator 99 | type: ClusterIP 100 | parameters: 101 | - description: image tag 102 | value: centos7-2.4 103 | required: true 104 | name: CO_IMAGE_TAG -------------------------------------------------------------------------------- /crunchy/db/db.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Name: inventory; Type: TABLE; Schema: public; Owner: postgres 3 | -- 4 | 5 | CREATE TABLE inventory ( 6 | inv_id bigint NOT NULL, 7 | item_id bigint, 8 | qty integer 9 | ); 10 | 11 | 12 | ALTER TABLE inventory OWNER TO postgres; 13 | 14 | -- 15 | -- Name: inventory_inv_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 16 | -- 17 | 18 | CREATE SEQUENCE inventory_inv_id_seq 19 | START WITH 1 20 | INCREMENT BY 1 21 | NO MINVALUE 22 | NO MAXVALUE 23 | CACHE 1; 24 | 25 | 26 | ALTER TABLE inventory_inv_id_seq OWNER TO postgres; 27 | 28 | -- 29 | -- Name: inventory_inv_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres 30 | -- 31 | 32 | ALTER SEQUENCE inventory_inv_id_seq OWNED BY inventory.inv_id; 33 | 34 | -- 35 | -- Name: product; Type: TABLE; Schema: public; Owner: postgres 36 | -- 37 | 38 | CREATE TABLE product ( 39 | item_id bigint NOT NULL, 40 | description character varying(2000), 41 | name character varying(255), 42 | price double precision NOT NULL 43 | ); 44 | 45 | 46 | ALTER TABLE product OWNER TO postgres; 47 | 48 | -- 49 | -- Name: product_item_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 50 | -- 51 | 52 | CREATE SEQUENCE product_item_id_seq 53 | START WITH 1 54 | INCREMENT BY 1 55 | NO MINVALUE 56 | NO MAXVALUE 57 | CACHE 1; 58 | 59 | 60 | ALTER TABLE product_item_id_seq OWNER TO postgres; 61 | 62 | -- 63 | -- Name: product_item_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres 64 | -- 65 | 66 | ALTER SEQUENCE product_item_id_seq OWNED BY product.item_id; 67 | 68 | -- 69 | -- Name: inventory inv_id; Type: DEFAULT; Schema: public; Owner: postgres 70 | -- 71 | 72 | ALTER TABLE ONLY inventory ALTER COLUMN inv_id SET DEFAULT nextval('inventory_inv_id_seq'::regclass); 73 | 74 | 75 | -- 76 | -- Name: product item_id; Type: DEFAULT; Schema: public; Owner: postgres 77 | -- 78 | 79 | ALTER TABLE ONLY product ALTER COLUMN item_id SET DEFAULT nextval('product_item_id_seq'::regclass); 80 | 81 | 82 | -- 83 | -- Name: inventory inventory_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres 84 | -- 85 | 86 | ALTER TABLE ONLY inventory 87 | ADD CONSTRAINT inventory_pkey PRIMARY KEY (inv_id); 88 | 89 | 90 | -- 91 | -- Name: product product_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres 92 | -- 93 | 94 | ALTER TABLE ONLY product 95 | ADD CONSTRAINT product_pkey PRIMARY KEY (item_id); 96 | 97 | 98 | -- 99 | -- Import product table data 100 | -- 101 | 102 | insert into PRODUCT (item_id, name, description, price) values (329299, 'Red Fedora', 'Official Red Hat Fedora', 34.99); 103 | insert into PRODUCT (item_id, name, description, price) values (329199, 'Forge Laptop Sticker', 'JBoss Community Forge Project Sticker', 8.50); 104 | insert into PRODUCT (item_id, name, description, price) values (165613, 'Solid Performance Polo', 'Moisture-wicking, antimicrobial 100% polyester design wicks for life of garment. No-curl, rib-knit collar; special collar band maintains crisp fold; three-button placket with dyed-to-match buttons; hemmed sleeves; even bottom with side vents; Import. Embroidery. Red Pepper.',17.80); 105 | insert into PRODUCT (item_id, name, description, price) values (165614, 'Ogio Caliber Polo', 'Moisture-wicking 100% polyester. Rib-knit collar and cuffs; Ogio jacquard tape inside neck; bar-tacked three-button placket with Ogio dyed-to-match buttons; side vents; tagless; Ogio badge on left sleeve. Import. Embroidery. Black.', 28.75); 106 | insert into PRODUCT (item_id, name, description, price) values (165954, '16 oz. Vortex Tumbler', 'Double-wall insulated, BPA-free, acrylic cup. Push-on lid with thumb-slide closure; for hot and cold beverages. Holds 16 oz. Hand wash only. Imprint. Clear.', 6.00); 107 | insert into PRODUCT (item_id, name, description, price) values (444434, 'Pebble Smart Watch', 'Smart glasses and smart watches are perhaps two of the most exciting developments in recent years.', 24.00); 108 | insert into PRODUCT (item_id, name, description, price) values (444435, 'Oculus Rift', 'The world of gaming has also undergone some very unique and compelling tech advances in recent years. Virtual reality, the concept of complete immersion into a digital universe through a special headset, has been the white whale of gaming and digital technology ever since Geekstakes Oculus Rift GiveawayNintendo marketed its Virtual Boy gaming system in 1995.Lytro',106.00 ); 109 | insert into PRODUCT (item_id, name, description, price) values (444436, 'Lytro Camera', 'Consumers who want to up their photography game are looking at newfangled cameras like the Lytro Field camera, designed to take photos with infinite focus, so you can decide later exactly where you want the focus of each image to be.', 44.30); 110 | 111 | 112 | -- 113 | -- Import inventory table data 114 | -- 115 | 116 | insert into INVENTORY (inv_id, item_id, qty) values (345452, 329299, 10); 117 | insert into INVENTORY (inv_id, item_id, qty) values (264563, 329199, 30); 118 | insert into INVENTORY (inv_id, item_id, qty) values (312313, 165613, 13); 119 | insert into INVENTORY (inv_id, item_id, qty) values (234256, 165614, 21); 120 | insert into INVENTORY (inv_id, item_id, qty) values (341657, 165954, 16); 121 | insert into INVENTORY (inv_id, item_id, qty) values (243256, 444434, 93); 122 | insert into INVENTORY (inv_id, item_id, qty) values (124556, 444435, 194); 123 | insert into INVENTORY (inv_id, item_id, qty) values (435213, 444436, 5); 124 | -------------------------------------------------------------------------------- /crunchy/deploy-HA-db.md: -------------------------------------------------------------------------------- 1 | # create a postgres cluster 2 | ``` 3 | pgo create cluster postgres-ha --password admin 4 | ``` 5 | create the database objects by running the DDLs 6 | This is a necessary steps to be execute outside of an app pod because the credential that the app uses will be rotated by Vault and it is not possible to delete an account that has credential objects. 7 | We are going to create the DDLs manually. A more container native-approach would be to automate the creation at provisoning time potentially. 8 | ``` 9 | oc project pgo 10 | pod=`oc get pods | grep postgres-ha-[0-9] | awk '{print $1}'` 11 | oc rsync ./crunchy/db/ $pod:/tmp --no-perms 12 | oc rsh $pod psql -U postgres -f /tmp/db.sql -d userdb 13 | 14 | ``` -------------------------------------------------------------------------------- /crunchy/deploy-cruncy.md: -------------------------------------------------------------------------------- 1 | # install the postegresql operator cli 2 | ``` 3 | curl -L -o /tmp/postgres-operator.2.4.tar.gz https://github.com/CrunchyData/postgres-operator/releases/download/2.4/postgres-operator.2.4.tar.gz 4 | tar -zxvf /tmp/postgres-operator.2.4.tar.gz -C /tmp 5 | sudo cp /tmp/pgo /usr/bin 6 | ``` 7 | 8 | # install the postgresql operator 9 | 10 | ``` 11 | oc new-project pgo 12 | oc adm policy add-cluster-role-to-user cluster-admin -z default 13 | oc create -f ./crunchy/crunchy-pvc.yaml 14 | 15 | oc create configmap apiserver-conf \ 16 | --from-file=./crunchy/conf/apiserver/pgouser \ 17 | --from-file=./crunchy/conf/apiserver/pgo.yaml \ 18 | --from-file=./crunchy/conf/apiserver/pgo.load-template.json \ 19 | --from-file=./crunchy/conf/apiserver/pgo.lspvc-template.json 20 | 21 | oc create configmap operator-conf \ 22 | --from-file=./crunchy/conf/postgres-operator/backup-job.json \ 23 | --from-file=./crunchy/conf/postgres-operator/rmdata-job.json \ 24 | --from-file=./crunchy/conf/postgres-operator/pvc.json \ 25 | --from-file=./crunchy/conf/postgres-operator/pvc-storageclass.json \ 26 | --from-file=./crunchy/conf/postgres-operator/cluster/1 27 | 28 | oc process -f ./crunchy/crunchy-template.yaml | oc apply -f - 29 | oc create route reencrypt --service=postgres-operator 30 | ``` 31 | 32 | # Configure the pgo client to talk to the crunchy operator 33 | 34 | ``` 35 | cat ~/.pgouser << EOF 36 | pgoadmin:pgoadmin 37 | EOF 38 | export CO_NAMESPACE=pgo 39 | export CO_APISERVER_URL=https://`oc get route | grep -m1 postgres-operator | awk '{print $2}'` 40 | pod=`oc get pods | grep postgres-operator | awk '{print $1}'` 41 | oc exec $pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt >> /tmp/ca.crt 42 | oc exec $pod -- cat /config/server.crt >> /tmp/server.crt 43 | oc exec $pod -- cat /config/server.key >> /tmp/server.key 44 | export PGO_CA_CERT=/tmp/ca.crt 45 | export PGO_CLIENT_CERT=/tmp/server.crt 46 | export PGO_CLIENT_KEY=/tmp/server.key 47 | ``` 48 | 49 | # testing 50 | 51 | ``` 52 | pgo version 53 | ``` 54 | should return something like: 55 | ``` 56 | pgo client version 2.4 57 | apiserver version 2.4 58 | ``` 59 | -------------------------------------------------------------------------------- /istio/README.md: -------------------------------------------------------------------------------- 1 | 2 | Istio Installation 3 | ================== 4 | 5 | ``` 6 | oc new-project istio-system 7 | curl -L https://git.io/getLatestIstio | sh - 8 | oc adm policy add-scc-to-user anyuid -z istio-ingress-service-account -n istio-system 9 | oc adm policy add-scc-to-user anyuid -z istio-egress-service-account -n istio-system 10 | oc adm policy add-scc-to-user anyuid -z default -n istio-system 11 | oc apply -f install/kubernetes/istio.yaml 12 | oc expose svc istio-ingress 13 | oc apply -f install/kubernetes/addons/prometheus.yaml 14 | oc apply -f install/kubernetes/addons/grafana.yaml 15 | oc apply -f install/kubernetes/addons/servicegraph.yaml 16 | ## Workaround for servicegraph bug https://github.com/istio/issues/issues/179 17 | oc set image deploy/servicegraph servicegraph="docker.io/istio/servicegraph:0.4.0" 18 | oc expose svc servicegraph 19 | oc expose svc grafana 20 | oc expose svc prometheus 21 | oc process -f https://raw.githubusercontent.com/jaegertracing/jaeger-openshift/master/all-in-one/jaeger-all-in-one-template.yml | oc create -f - 22 | ``` -------------------------------------------------------------------------------- /istio/addons.md: -------------------------------------------------------------------------------- 1 | 2 | Istio Addons Installation 3 | ================== 4 | 5 | * Deploy additional add-ons, namely Prometheus, Grafana, Service Graph and Jaeger. 6 | 7 | ``` 8 | oc project istio-system 9 | oc apply -f https://raw.githubusercontent.com/istio/istio/0.4.0/install/kubernetes/addons/prometheus.yaml 10 | oc apply -f https://raw.githubusercontent.com/istio/istio/0.4.0/install/kubernetes/addons/grafana.yaml 11 | oc apply -f https://raw.githubusercontent.com/istio/istio/0.4.0/install/kubernetes/addons/servicegraph.yaml 12 | oc process -f https://raw.githubusercontent.com/jaegertracing/jaeger-openshift/master/all-in-one/jaeger-all-in-one-template.yml | oc apply -f - 13 | oc expose svc grafana 14 | oc expose svc servicegraph 15 | oc expose svc prometheus 16 | ``` 17 | 18 | References 19 | ---------- 20 | 21 | * [Evaluate Istio on OpenShift](https://blog.openshift.com/evaluate-istio-openshift/) 22 | -------------------------------------------------------------------------------- /istio/enabling-tls.md: -------------------------------------------------------------------------------- 1 | # Enabling mTLS Between Inventory and Catalog 2 | 3 | enabling mutual TLS authentication is Istio is as simple as adding an annotation to the service that groups the server pods. 4 | The annotation will look like the following `auth.istio.io/8080: MUTUAL_TLS` 5 | 6 | Here is how the product inventory catalog looks like: 7 | ``` 8 | apiVersion: v1 9 | kind: Service 10 | metadata: 11 | name: product-catalog 12 | annotations: 13 | auth.istio.io/8080: MUTUAL_TLS 14 | spec: 15 | ports: 16 | - port: 8080 17 | protocol: TCP 18 | targetPort: 8080 19 | selector: 20 | app: product-catalog 21 | ``` -------------------------------------------------------------------------------- /istio/enabling_sso.md: -------------------------------------------------------------------------------- 1 | # Enabling istio OAuth-based Authentication with JWT binding using RH-SSO as OAuth provider 2 | 3 | create the istio policy 4 | ``` 5 | oc new-project product 6 | export fqdn=`oc get route -n rh-sso | grep secure-sso | awk '{print $2}'` 7 | oc process -f ./spring/product-catalog/src/istio/product-catalog-auth_config.yaml -p RH_SSO_ROUTE=$fqdn | oc apply -f - 8 | istioctl create -f ./spring/product-catalog/src/istio/mixer-rule-only-authorized.yaml 9 | ``` 10 | 11 | The first set of commands creates an JWT oauth authentication rule that points to rh-sso as the oauth provider and then binds the rule to the catalog service. 12 | The second command creates a rule that will allow only aushorized users to access the service -------------------------------------------------------------------------------- /istio/istio-sso.md: -------------------------------------------------------------------------------- 1 | # Istio/SSO Integration Notes 2 | 3 | ## Current Status 4 | 5 | * Product Catalog Spring Boot application runs with an Istio Proxy sidecar 6 | * Authorization policy in place to intercept HTTP calls for application which will: 7 | * Validate JWT token 8 | * Make backend call to SSO/Keycloak to valide JWT signature based on public key (which may be cached) 9 | 10 | ## Known Issues/Findings 11 | 12 | * [Bug: forward_jwt doesn't seem to forward Authorization header](https://github.com/istio/proxy/issues/986) 13 | * Awaiting implementation even though documented 14 | 15 | * Istio rules do not allow for checking of missing `Authorization` header. This needs to be done elsewhere such as the Spring Controller. 16 | 17 | * [Bug: istioctl support for Openshift DeploymentConfig](https://github.com/istio/issues/issues/29) 18 | * Need to use Deployment instead (see Kamesh's [example project](https://github.com/kameshsampath/istio-keycloak-demo/blob/98302468c0bb9cf4204b41cf2f04672c561eab05/cars-api/src/istio/cars-api-0.0.1-all.yml) 19 | 20 | * [Bug: EndUserAuthenticationSpec not allowing http](https://github.com/istio/istio/issues/2668) 21 | * Workaround is to use `kubectl` rather than `istioctl` 22 | 23 | * [Undocumented: Need to use denier status code 16 to return 401 Unauthorized](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto#L103) 24 | * Will return 500 error otherwise making you think there was actually an error when there wasn't 25 | 26 | * [Bug: Liveness/Readiness Probe Istio causing stream of errors in proxy log](https://github.com/istio/istio/issues/2628) 27 | * Disable probes for deployment or use shell script to get status internally 28 | * These errors will prevent proxy from running normally! 29 | 30 | * [Bug: Disable Liveness/Readiness Probe on HTTP port 8080 or Spring Boot container will not go live](https://github.com/kameshsampath/istio-keycloak-demo/issues/3) 31 | 32 | * Bug?: Updating Auth Policies may be unreliable requiring restarting destination service pod or even Istio itself 33 | 34 | * Bug?: Errors coming from Istio/Proxy logs which turn out to be red herrings 35 | * `ERROR: logging before flag.Parse` 36 | * `[libprotobuf ERROR external/mixerclient_git/src/quota_cache.cc:119] Quota response did not have quota for: RequestCount` 37 | 38 | 39 | # Implementation/Testing 40 | 41 | 1) Undeploy existing Product Catalog DC/Route/Service 42 | 43 | Will use separate deployment to show Istio/SSO integration for Product Catalog as they have not yet been merged into one. This is a TODO item I didn't have time for. 44 | 45 | ``` 46 | oc delete -f spring/product-catalog/src/main/fabric8/route.yaml 47 | oc delete -f spring/product-catalog/src/main/fabric8/service.yaml 48 | oc delete -f spring/product-catalog/src/main/fabric8/deploymentconfig.yaml 49 | ``` 50 | 51 | 2) Delete any remaining Product Catalog Deployment/Pods 52 | 53 | The alternate deployment will reuse Product Catalog image from previous Fabric8 build. 54 | 55 | 3) Deploy Istio/SSO Integration 56 | 57 | Requires using `istioctl` which is part of the Istio download package. 58 | 59 | ``` 60 | oc apply -f spring/product-catalog/src/istio/product-catalog-auth_config.yaml 61 | istioctl create -f spring/product-catalog/src/istio/mixer-rule-only-authorized.yaml 62 | ``` 63 | 64 | 4) Run alternate Product Catalog Deployment 65 | 66 | Deployment, Service, Route will be created. 67 | 68 | ``` 69 | oc apply -f spring/product-catalog/src/istio/istio-product-catalog-0.0.1-all.yml 70 | ``` 71 | 72 | 5) Go to Frontend application and test Invoke functionality to make sure it still works. 73 | 74 | Make sure you log out and back in of SSO to have a fresh session (not expired JWT) 75 | 76 | If there is error coming from invoke, then these instructions have failed and more investigation will be required. Check the logs for Product Inventory for errors including a 403 error code. 77 | 78 | 6) Proceed to validating token validation is working 79 | 80 | a) `oc rsh product-inventory-XXX bash` to log into Product Inventory container, where we will perform the `curl` command internal to OpenShift. 81 | 82 | b) Copy `curl` command from the web browser from the Frontend application 83 | 84 | c) Paste into text editor to make some tweaks 85 | 86 | d) Change URL to be `http://product-catalog.product.svc:8080/product` 87 | 88 | e) Remove `| jq` 89 | 90 | f) Add `-vvv` switch to curl command for verbose output 91 | 92 | g) Run the new `curl` command inside of the `product-inventory` container. You should see results coming back from the Product Catalog Service. 93 | 94 | h) Go back to text editor. Change the JWT in the curl command by replacing the first character with something else. 95 | 96 | i) Run the curl command again inside of the product-inventory container. You should be an invalid header error. This is coming from Istio and proves the JWT is being validated. 97 | 98 | Extra Credit: 99 | 100 | j) Try a JWT you know to be expired. You should get a response from Istio saying it's expired. 101 | 102 | k) Replace the signature of your JWT with a signature from a token which is generated from another realm or system. You will get an invalide signature error. This comes from Istio contacting SSO for the public key, and then it will validate the signature is valid for that JWT. This happens for all tokens that pass the initial validation as the signature validation is the last step. 103 | 104 | ### Helpful Hints 105 | 106 | * Use jwt.io to view the contents of your JWT token. 107 | -------------------------------------------------------------------------------- /media/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step1.png -------------------------------------------------------------------------------- /media/step10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step10.png -------------------------------------------------------------------------------- /media/step11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step11.png -------------------------------------------------------------------------------- /media/step12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step12.png -------------------------------------------------------------------------------- /media/step13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step13.png -------------------------------------------------------------------------------- /media/step14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step14.png -------------------------------------------------------------------------------- /media/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step2.png -------------------------------------------------------------------------------- /media/step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step3.png -------------------------------------------------------------------------------- /media/step4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step4.png -------------------------------------------------------------------------------- /media/step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step5.png -------------------------------------------------------------------------------- /media/step6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step6.png -------------------------------------------------------------------------------- /media/step7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step7.png -------------------------------------------------------------------------------- /media/step8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step8.png -------------------------------------------------------------------------------- /media/step9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/media/step9.png -------------------------------------------------------------------------------- /microsegmentation/README.md: -------------------------------------------------------------------------------- 1 | # Microsegmentation 2 | 3 | Microsegmentation means creating network segments of one by creating custom firewall rules per pod. 4 | In OpenShift this can be done using NetworkPolicy. 5 | In order not to create network policy manually we will use a microsgmentation controller which will create the networkpolicies based on our services configuration. 6 | More information on the network policy controller (which is not production ready) can be found [here](https://github.com/raffaelespazzoli/microsegmentationcontroller) 7 | 8 | This microsgmentation controller is built on the metacontroller framework. 9 | 10 | Deploy the metacontroller: 11 | ``` 12 | oc new-project metacontroller 13 | oc apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/kube-metacontroller/master/manifests/metacontroller-rbac.yaml 14 | oc apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/kube-metacontroller/master/manifests/metacontroller.yaml 15 | ``` 16 | 17 | Build and deploy the microsegmentation controller 18 | ``` 19 | oc new-build registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift~https://github.com/raffaelespazzoli/microsegmentationcontroller --name=microsegmentation-controller -F 20 | oc apply -f https://github.com/raffaelespazzoli/microsegmentationcontroller/blob/master/src/main/kubernetes/microsegmentation-controller.yaml 21 | ``` 22 | 23 | Now, in order to have the networkpolicy objects created our services should be annotated, here is an example: 24 | ``` 25 | annotations: 26 | io.raffa.microsegmentation: 'true' 27 | io.raffa.microsegmentation.additional-ports: 15000/tcp 28 | ``` 29 | 30 | notice the additional port that allows istio to control the envoy sidecar 31 | -------------------------------------------------------------------------------- /openshift/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-cop/container-native-spring-postgresql/dc2fa4b8613ebd6805b8574c341b8a4fd851767c/openshift/README.md -------------------------------------------------------------------------------- /spring/README.md: -------------------------------------------------------------------------------- 1 | # container-native-spring-postgresql 2 | 3 | Here are the steps for the installation: 4 | 5 | ## Build and deploy the product catalog 6 | 7 | ``` 8 | oc new-project product 9 | oc adm policy add-scc-to-user privileged -z default 10 | oc new-build registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift --binary=true --name product-catalog 11 | oc start-build product-catalog -F --from-dir=./spring/product-catalog 12 | oc apply -f ./spring/product-catalog/src/main/fabric8/service.yml 13 | oc apply -f ./spring/product-catalog/src/main/fabric8/deploymentconfig.yml 14 | ``` 15 | 16 | See also here for more details: [Build and deploy the product-catalog application](./product-catalog/README.adoc) 17 | 18 | ## Build and deploy the product inventory 19 | 20 | ``` 21 | oc new-build registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift --binary=true --name product-inventory 22 | oc start-build product-inventory -F --from-dir=./spring/product-inventory 23 | fqdn=`oc get route -n rh-sso | grep secure-sso | awk '{print $2}'` 24 | key=`kcadm.sh get keys --trustpass=changeit -r master --fields 'keys(publicKey)' | jq .keys[0].publicKey -r` 25 | secret=`kcadm.sh get --trustpass=changeit clients/b5e0526f-25b5-452c-9d66-e56515402e7f/client-secret --fields 'value' --format csv --noquotes` 26 | oc process -f ./spring/product-inventory/src/main/fabric8/configmap.yml -p RH_SSO_REALM_KEY=$key RH_SSO_URL=$fqdn RH_SSO_CREDENTIAL_SECRET=$secret | oc apply -f - 27 | oc create -f ./spring/product-inventory/src/main/fabric8/service.yml 28 | oc create -f ./spring/product-inventory/src/main/fabric8/route.yml 29 | oc create -f ./spring/product-inventory/src/main/fabric8/deploymentconfig.yml 30 | ``` 31 | 32 | See also here for more information [Build and deploy the product-inventory application](./product-inventory/README.adoc) 33 | 34 | 35 | ## Build and deploy the front-end 36 | 37 | ``` 38 | secret=`kcadm.sh get --trustpass=changeit clients/b5e0526f-25b5-452c-9d66-e56515402e7f/client-secret --fields 'value' --format csv --noquotes` 39 | mvn -f ./spring/product-frontend clean fabric8:deploy -Popenshift -DSSO_AUTH_SERVER_URL=$(oc get route secure-sso -n rh-sso -o jsonpath='{"https://"}{.spec.host}{"/auth"}') -DPRODUCT_INVENTORY_SERVICE_URL=$(oc get route product-inventory -o jsonpath='{"https://"}{.spec.host}') -DSSO_CLIENT_SECRET=$secret 40 | ``` 41 | 42 | See also here for more details: [Build and deploy the product-frontend application](./product-frontend/README.adoc) -------------------------------------------------------------------------------- /spring/accessing_pg.md: -------------------------------------------------------------------------------- 1 | # Configuring the app to access the db. 2 | 3 | The application needs to exchange a service token for a vault token. 4 | With that Vault token it can request a database set of credential. 5 | Vault dynamically generate it and return it to the application. 6 | At this point the application can connect to the database. 7 | Both the vault token and the database credential have a TTL. 8 | To the application also needs to renew the vault token and retrieve the new database credential when it's rotated by Vault. 9 | All of this is managed by a couple of libraries in spring that you can import this way: 10 | ``` 11 | 12 | org.springframework.cloud 13 | spring-cloud-starter-vault-config 14 | 15 | 16 | org.springframework.cloud 17 | spring-cloud-vault-config-databases 18 | 19 | ``` 20 | in order to configure the app to use spring you need to do add the following to your `bootstrap.yaml`: 21 | ``` 22 | spring.cloud.vault: 23 | host: vault.vault.svc.cluster.local 24 | port: 8200 25 | scheme: https 26 | authentication: KUBERNETES 27 | kubernetes: 28 | role: product-catalog 29 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 30 | config: 31 | order: -10 32 | generic: 33 | enabled: false 34 | postgresql: 35 | enabled: true 36 | role: pg-readwrite 37 | backend: database 38 | username-property: spring.datasource.username 39 | password-property: spring.datasource.password 40 | ``` 41 | Vault is considered a property source. `spring.datasource.username` and `spring.datasource.password` are the properties that will be populated by Spring from Vault and can be used as normal properties. -------------------------------------------------------------------------------- /spring/adding_istio_sidecar.md: -------------------------------------------------------------------------------- 1 | # Configring the app to use istio 2 | 3 | Istio offeres two ways of injecting the necessary configuratio to allow your pod to join the service mesh 4 | 5 | 1. istioctl kube-inject. This doesn't work with DeploymentConfigs 6 | 2. kubernetes initializers. This is available only in kubernetes 1.9 7 | 8 | None of these methods worked for us to we manually changed our deployment configs to add istio. 9 | The relevant configurations are the following: 10 | ``` 11 | initcontainers: 12 | ... 13 | - args: 14 | - -p 15 | - "15001" 16 | - -u 17 | - "1337" 18 | image: docker.io/istio/proxy_init:0.4.0 19 | imagePullPolicy: IfNotPresent 20 | name: istio-init 21 | resources: {} 22 | securityContext: 23 | capabilities: 24 | add: 25 | - NET_ADMIN 26 | privileged: true 27 | - args: 28 | - -c 29 | - sysctl -w kernel.core_pattern=/etc/istio/proxy/core.%e.%p.%t && ulimit -c 30 | unlimited 31 | command: 32 | - /bin/sh 33 | image: alpine 34 | imagePullPolicy: IfNotPresent 35 | name: enable-core-dump 36 | resources: {} 37 | securityContext: 38 | privileged: true 39 | ... 40 | containers: 41 | ... 42 | - args: 43 | - proxy 44 | - sidecar 45 | - -v 46 | - "2" 47 | - --configPath 48 | - /etc/istio/proxy 49 | - --binaryPath 50 | - /usr/local/bin/envoy 51 | - --serviceCluster 52 | - product-catalog 53 | - --drainDuration 54 | - 45s 55 | - --parentShutdownDuration 56 | - 1m0s 57 | - --discoveryAddress 58 | - istio-pilot.istio-system:15003 59 | - --discoveryRefreshDelay 60 | - 1s 61 | - --zipkinAddress 62 | - zipkin.istio-system:9411 63 | - --connectTimeout 64 | - 10s 65 | - --statsdUdpAddress 66 | - istio-mixer.istio-system:9125 67 | - --proxyAdminPort 68 | - "15000" 69 | - --controlPlaneAuthPolicy 70 | - NONE 71 | env: 72 | - name: POD_NAME 73 | valueFrom: 74 | fieldRef: 75 | fieldPath: metadata.name 76 | - name: POD_NAMESPACE 77 | valueFrom: 78 | fieldRef: 79 | fieldPath: metadata.namespace 80 | - name: INSTANCE_IP 81 | valueFrom: 82 | fieldRef: 83 | fieldPath: status.podIP 84 | image: docker.io/istio/proxy_debug:0.4.0 85 | imagePullPolicy: IfNotPresent 86 | name: istio-proxy 87 | resources: {} 88 | securityContext: 89 | privileged: true 90 | readOnlyRootFilesystem: false 91 | runAsUser: 1337 92 | volumeMounts: 93 | - mountPath: /etc/istio/proxy 94 | name: istio-envoy 95 | - mountPath: /etc/certs/ 96 | name: istio-certs 97 | readOnly: true 98 | ... 99 | volumes: 100 | ... 101 | - name: istio-envoy 102 | emptyDir: 103 | medium: Memory 104 | sizeLimit: "0" 105 | - name: istio-certs 106 | secret: 107 | optional: true 108 | secretName: istio.default 109 | ``` -------------------------------------------------------------------------------- /spring/product-catalog/README.adoc: -------------------------------------------------------------------------------- 1 | == CRUD - Spring Boot - Product catalog 2 | 3 | IMPORTANT: For more details on using this application with a single-node OpenShift cluster refer below. This application requires Java 8 JDK or greater and Maven 3.3.x or greater. 4 | 5 | == Building Deploying and Running the Application 6 | 7 | To deploy your application to a running single-node OpenShift cluster: 8 | [source,bash,options="nowrap",subs="attributes+"] 9 | ---- 10 | # oc project product 11 | 12 | oc adm policy add-scc-to-user privileged -z default 13 | 14 | $ oc new-build registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift~https://github.com/murphye/container-native-spring-postgresql.git --context-dir=spring/product-catalog --name product-catalog 15 | 16 | #From the git repo folder run the below commands: 17 | 18 | $ oc apply -f ./spring/product-catalog/src/main/fabric8/service.yml 19 | 20 | $ oc apply -f ./spring/product-catalog/src/main/fabric8/route.yml 21 | 22 | $ oc apply -f ./spring/product-catalog/src/main/fabric8/deploymentconfig.yml 23 | ---- 24 | 25 | == Interacting with the Application 26 | 27 | To interact with your booster while it's running on a Single-node OpenShift Cluster, you first need to obtain it's URL: 28 | 29 | [source,bash,options="nowrap",subs="attributes+"] 30 | ---- 31 | $ oc get route product -o jsonpath={$.spec.host} 32 | 33 | product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME 34 | ---- 35 | 36 | 37 | You can use the form at your application's url or you can use the `curl` command: 38 | 39 | .List all entries in the database 40 | [source,bash,options="nowrap",subs="attributes+"] 41 | ---- 42 | $ curl http://product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME/product 43 | 44 | ---- 45 | 46 | .Retrieve an entry with a specific ID 47 | [source,bash,options="nowrap",subs="attributes+"] 48 | ---- 49 | curl http://product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME/product/{itemId} 50 | 51 | ---- 52 | 53 | 54 | .Create a new entry: 55 | [source,bash,options="nowrap",subs="attributes+"] 56 | ---- 57 | curl -H "Content-Type: application/json" -X POST -d '{"name":"Product", "description" : "Product Description", "price" : "10.00"}' http://product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME/product 58 | 59 | ---- 60 | 61 | 62 | .Update an Entry 63 | [source,bash,options="nowrap",subs="attributes+"] 64 | ---- 65 | curl -H "Content-Type: application/json" -X PUT -d '{"itemId":"354255", "name":"Product", "description" : "Product Description", "price" : "10.00"}' http://product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME/product/(itemId) 66 | 67 | ---- 68 | 69 | 70 | .Delete an Entry: 71 | [source,bash,options="nowrap",subs="attributes+"] 72 | ---- 73 | curl -X DELETE http://product-catalog-product.LOCAL_OPENSHIFT_HOSTNAME/product/{itemId} 74 | ---- 75 | 76 | NOTE: If you receive an HTTP Error code `503` as a response after executing these commands, it means that the application is not ready yet. 77 | -------------------------------------------------------------------------------- /spring/product-catalog/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.redhat 7 | product-catalog 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | Product Catalog 12 | Sample Product catalog 13 | 14 | 15 | io.openshift 16 | booster-parent 17 | 11 18 | 19 | 20 | 21 | 1.5.8.RELEASE 22 | 9.4.1212 23 | 1.5.8.Final 24 | 25 | 26 | 27 | 28 | 29 | me.snowdrop 30 | spring-boot-bom 31 | ${spring-boot.bom.version} 32 | pom 33 | import 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-vault-dependencies 38 | 1.1.0.RELEASE 39 | import 40 | pom 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-web 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-data-jpa 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-actuator 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | com.h2database 65 | h2 66 | test 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-maven-plugin 75 | ${maven.spring-boot.plugin.version} 76 | 77 | 78 | local 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | local 88 | 89 | true 90 | 91 | 92 | 93 | com.h2database 94 | h2 95 | runtime 96 | 97 | 98 | 99 | 100 | openshift 101 | 102 | 103 | org.postgresql 104 | postgresql 105 | ${postgresql.version} 106 | runtime 107 | 108 | 109 | 110 | org.springframework.cloud 111 | spring-cloud-starter-vault-config 112 | 113 | 114 | org.springframework.cloud 115 | spring-cloud-vault-config-databases 116 | 117 | 118 | 119 | 120 | 121 | org.springframework.boot 122 | spring-boot-maven-plugin 123 | 124 | 125 | 126 | repackage 127 | 128 | 129 | 130 | 131 | 132 | io.fabric8 133 | fabric8-maven-plugin 134 | 135 | 136 | fmp 137 | package 138 | 139 | resource 140 | build 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /spring/product-catalog/src/istio/istio-product-catalog-0.0.1-all.yml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | fabric8.io/metrics-path: dashboard/file/kubernetes-pods.json/?var-project=product-catalog&var-version=0.0.1 6 | sidecar.istio.io/status: injected-version-root@cc5c34bbd1ee-0.4.0-24089ea97c8d244493c93b499a666ddf4010b547 7 | creationTimestamp: null 8 | labels: 9 | app: product-catalog 10 | group: com.redhat.developers 11 | provider: fabric8 12 | version: 0.0.1 13 | name: product-catalog 14 | spec: 15 | replicas: 1 16 | selector: 17 | matchLabels: 18 | app: product-catalog 19 | group: com.redhat.developers 20 | provider: fabric8 21 | strategy: {} 22 | template: 23 | metadata: 24 | annotations: 25 | fabric8.io/metrics-path: dashboard/file/kubernetes-pods.json/?var-project=product-catalog&var-version=0.0.1 26 | sidecar.istio.io/status: injected-version-root@cc5c34bbd1ee-0.4.0-24089ea97c8d244493c93b499a666ddf4010b547 27 | creationTimestamp: null 28 | labels: 29 | app: product-catalog 30 | group: com.redhat.developers 31 | provider: fabric8 32 | version: 0.0.1 33 | spec: 34 | containers: 35 | - env: 36 | - name: KUBERNETES_NAMESPACE 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: metadata.namespace 40 | - name: JAVA_OPTIONS 41 | value: -Dspring.profiles.active=openshift -Djavax.net.ssl.trustStore=/var/run/secrets/java.io/keystores/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit 42 | image: 172.30.1.1:5000/product/product-catalog:latest 43 | imagePullPolicy: IfNotPresent 44 | name: spring-boot 45 | ports: 46 | - containerPort: 8080 47 | name: http 48 | protocol: TCP 49 | - containerPort: 9779 50 | name: prometheus 51 | protocol: TCP 52 | - containerPort: 8778 53 | name: jolokia 54 | protocol: TCP 55 | resources: {} 56 | securityContext: 57 | privileged: false 58 | volumeMounts: 59 | - name: keystore-volume 60 | mountPath: /var/run/secrets/java.io/keystores 61 | 62 | - args: 63 | - proxy 64 | - sidecar 65 | - -v 66 | - "2" 67 | - --configPath 68 | - /etc/istio/proxy 69 | - --binaryPath 70 | - /usr/local/bin/envoy 71 | - --serviceCluster 72 | - product-catalog 73 | - --drainDuration 74 | - 45s 75 | - --parentShutdownDuration 76 | - 1m0s 77 | - --discoveryAddress 78 | - istio-pilot.istio-system:15003 79 | - --discoveryRefreshDelay 80 | - 1s 81 | - --zipkinAddress 82 | - zipkin.istio-system:9411 83 | - --connectTimeout 84 | - 10s 85 | - --statsdUdpAddress 86 | - istio-mixer.istio-system:9125 87 | - --proxyAdminPort 88 | - "15000" 89 | - --controlPlaneAuthPolicy 90 | - NONE 91 | env: 92 | - name: POD_NAME 93 | valueFrom: 94 | fieldRef: 95 | fieldPath: metadata.name 96 | - name: POD_NAMESPACE 97 | valueFrom: 98 | fieldRef: 99 | fieldPath: metadata.namespace 100 | - name: INSTANCE_IP 101 | valueFrom: 102 | fieldRef: 103 | fieldPath: status.podIP 104 | image: docker.io/istio/proxy_debug:0.4.0 105 | imagePullPolicy: IfNotPresent 106 | name: istio-proxy 107 | resources: {} 108 | securityContext: 109 | privileged: true 110 | readOnlyRootFilesystem: false 111 | runAsUser: 1337 112 | volumeMounts: 113 | - mountPath: /etc/istio/proxy 114 | name: istio-envoy 115 | - mountPath: /etc/certs/ 116 | name: istio-certs 117 | readOnly: true 118 | initContainers: 119 | - args: ['-c', "csplit -z -f crt- $ca_bundle '/-----BEGIN CERTIFICATE-----/' '{*}' && for file in crt-*; do keytool -import -noprompt -keystore $truststore_jks -file $file -storepass changeit -ali 120 | as service-$file; done"] 121 | name: pem-to-truststore 122 | image: registry.access.redhat.com/redhat-sso-7/sso71-openshift:1.1-16 123 | env: 124 | - name: ca_bundle 125 | value: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt 126 | - name: truststore_jks 127 | value: /var/run/secrets/java.io/keystores/truststore.jks 128 | - name: password 129 | value: changeit 130 | command: ['/bin/bash'] 131 | volumeMounts: 132 | - name: keystore-volume 133 | mountPath: /var/run/secrets/java.io/keystores 134 | - args: 135 | - -p 136 | - "15001" 137 | - -u 138 | - "1337" 139 | image: docker.io/istio/proxy_init:0.4.0 140 | imagePullPolicy: IfNotPresent 141 | name: istio-init 142 | resources: {} 143 | securityContext: 144 | capabilities: 145 | add: 146 | - NET_ADMIN 147 | privileged: true 148 | - args: 149 | - -c 150 | - sysctl -w kernel.core_pattern=/etc/istio/proxy/core.%e.%p.%t && ulimit -c 151 | unlimited 152 | command: 153 | - /bin/sh 154 | image: alpine 155 | imagePullPolicy: IfNotPresent 156 | name: enable-core-dump 157 | resources: {} 158 | securityContext: 159 | privileged: true 160 | volumes: 161 | - emptyDir: 162 | medium: Memory 163 | sizeLimit: "0" 164 | name: istio-envoy 165 | - name: istio-certs 166 | secret: 167 | optional: true 168 | secretName: istio.default 169 | - name: keystore-volume 170 | emptyDir: {} 171 | status: {} 172 | --- 173 | apiVersion: v1 174 | kind: Service 175 | metadata: 176 | labels: 177 | expose: "true" 178 | app: product-catalog 179 | provider: fabric8 180 | version: 0.0.1 181 | group: com.redhat.developers 182 | name: product-catalog 183 | spec: 184 | ports: 185 | - name: http 186 | port: 8080 187 | protocol: TCP 188 | targetPort: 8080 189 | selector: 190 | app: product-catalog 191 | provider: fabric8 192 | group: com.redhat.developers 193 | --- 194 | apiVersion: route.openshift.io/v1 195 | kind: Route 196 | metadata: 197 | labels: 198 | expose: "true" 199 | app: product-catalog 200 | provider: fabric8 201 | version: 0.0.1 202 | group: com.redhat.developers 203 | name: product-catalog 204 | spec: 205 | port: 206 | targetPort: 8080 207 | to: 208 | kind: Service 209 | name: product-catalog -------------------------------------------------------------------------------- /spring/product-catalog/src/istio/mixer-rule-only-authorized.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: "config.istio.io/v1alpha2" 3 | kind: denier 4 | metadata: 5 | name: productcatalog-handler 6 | namespace: product 7 | spec: 8 | status: 9 | code: 16 10 | message: You are not authorized to access the service 11 | --- 12 | apiVersion: "config.istio.io/v1alpha2" 13 | kind: checknothing 14 | metadata: 15 | name: productcatalog-denyrequest 16 | namespace: product 17 | spec: 18 | --- 19 | apiVersion: "config.istio.io/v1alpha2" 20 | kind: rule 21 | metadata: 22 | name: product-deny 23 | namespace: product 24 | spec: 25 | match: destination.labels["app"] == "product-catalog" && destination.namespace == "product" && (request.headers["authorization"]|"unauthorized") == "unauthorized" 26 | actions: 27 | - handler: productcatalog-handler.denier.product 28 | instances: [productcatalog-denyrequest.checknothing.product] -------------------------------------------------------------------------------- /spring/product-catalog/src/istio/product-catalog-auth_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Template 3 | metadata: 4 | name: product-istio-oauth-policy 5 | objects: 6 | - apiVersion: config.istio.io/v1alpha2 7 | kind: EndUserAuthenticationPolicySpec 8 | metadata: 9 | name: product-catalog-auth-policy 10 | namespace: product 11 | spec: 12 | jwts: 13 | - issuer: https://${RH_SSO_ROUTE}/auth/realms/master 14 | jwks_uri: https://${RH_SSO_SVC}:8443/auth/realms/master/protocol/openid-connect/certs 15 | audiences: 16 | - inventoryservice 17 | forward_jwt: true 18 | - apiVersion: config.istio.io/v1alpha2 19 | kind: EndUserAuthenticationPolicySpecBinding 20 | metadata: 21 | name: product-catalog-auth-policy-binding 22 | namespace: product 23 | spec: 24 | policies: 25 | - name: product-catalog-auth-policy 26 | namespace: product 27 | services: 28 | - name: product-catalog 29 | namespace: product 30 | parameters: 31 | - description: rh-sso route url 32 | required: true 33 | name: RH_SSO_ROUTE 34 | - description: rh-sso service url 35 | required: true 36 | value: secure-sso.rh-sso.svc 37 | name: RH_SSO_SVC -------------------------------------------------------------------------------- /spring/product-catalog/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Deployment 3 | metadata: 4 | name: product-catalog 5 | spec: 6 | triggers: 7 | - 8 | type: ConfigChange 9 | - 10 | type: ImageChange 11 | imageChangeParams: 12 | automatic: true 13 | containerNames: 14 | - product-catalog 15 | from: 16 | kind: ImageStreamTag 17 | namespace: product 18 | name: 'product-catalog:latest' 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | app: product-catalog 24 | spec: 25 | initContainers: 26 | - args: ['-c', "csplit -z -f crt- $ca_bundle '/-----BEGIN CERTIFICATE-----/' '{*}' && for file in crt-*; do keytool -import -noprompt -keystore $truststore_jks -file $file -storepass changeit -alias service-$file; done"] 27 | name: pem-to-truststore 28 | image: registry.access.redhat.com/redhat-sso-7/sso71-openshift:1.1-16 29 | env: 30 | - name: ca_bundle 31 | value: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt 32 | - name: truststore_jks 33 | value: /var/run/secrets/java.io/keystores/truststore.jks 34 | - name: password 35 | value: changeit 36 | command: ['/bin/bash'] 37 | volumeMounts: 38 | - name: keystore-volume 39 | mountPath: /var/run/secrets/java.io/keystores 40 | containers: 41 | - name: product-catalog 42 | image: "product/product-catalog:latest" 43 | imagePullPolicy: Always 44 | env: 45 | - name: JAVA_OPTIONS 46 | value: -Dspring.profiles.active=openshift -Djavax.net.ssl.trustStore=/var/run/secrets/java.io/keystores/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit 47 | volumeMounts: 48 | - name: keystore-volume 49 | mountPath: /var/run/secrets/java.io/keystores 50 | 51 | 52 | 53 | volumes: 54 | - name: keystore-volume 55 | emptyDir: {} 56 | - name: istio-envoy 57 | emptyDir: 58 | medium: Memory 59 | sizeLimit: "0" 60 | - name: istio-certs 61 | secret: 62 | optional: true 63 | secretName: istio.default -------------------------------------------------------------------------------- /spring/product-catalog/src/main/fabric8/deploymentconfig.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: DeploymentConfig 3 | metadata: 4 | name: product-catalog 5 | spec: 6 | triggers: 7 | - 8 | type: ConfigChange 9 | - 10 | type: ImageChange 11 | imageChangeParams: 12 | automatic: true 13 | containerNames: 14 | - product-catalog 15 | from: 16 | kind: ImageStreamTag 17 | namespace: product 18 | name: 'product-catalog:latest' 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | app: product-catalog 24 | spec: 25 | initContainers: 26 | - args: ['-c', "csplit -z -f crt- $ca_bundle '/-----BEGIN CERTIFICATE-----/' '{*}' && for file in crt-*; do keytool -import -noprompt -keystore $truststore_jks -file $file -storepass changeit -alias service-$file; done"] 27 | name: pem-to-truststore 28 | image: registry.access.redhat.com/redhat-sso-7/sso71-openshift:1.1-16 29 | env: 30 | - name: ca_bundle 31 | value: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt 32 | - name: truststore_jks 33 | value: /var/run/secrets/java.io/keystores/truststore.jks 34 | - name: password 35 | value: changeit 36 | command: ['/bin/bash'] 37 | volumeMounts: 38 | - name: keystore-volume 39 | mountPath: /var/run/secrets/java.io/keystores 40 | - args: 41 | - -p 42 | - "15001" 43 | - -u 44 | - "1337" 45 | image: docker.io/istio/proxy_init:0.5.0 46 | imagePullPolicy: IfNotPresent 47 | name: istio-init 48 | resources: {} 49 | securityContext: 50 | capabilities: 51 | add: 52 | - NET_ADMIN 53 | privileged: true 54 | containers: 55 | - name: product-catalog 56 | image: "product/product-catalog:latest" 57 | imagePullPolicy: Always 58 | env: 59 | - name: JAVA_OPTIONS 60 | value: -Dspring.profiles.active=openshift -Djavax.net.ssl.trustStore=/var/run/secrets/java.io/keystores/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit 61 | volumeMounts: 62 | - name: keystore-volume 63 | mountPath: /var/run/secrets/java.io/keystores 64 | 65 | - args: 66 | - proxy 67 | - sidecar 68 | - -v 69 | - "2" 70 | - --configPath 71 | - /etc/istio/proxy 72 | - --binaryPath 73 | - /usr/local/bin/envoy 74 | - --serviceCluster 75 | - product-catalog 76 | - --drainDuration 77 | - 45s 78 | - --parentShutdownDuration 79 | - 1m0s 80 | - --discoveryAddress 81 | - istio-pilot.istio-system:15003 82 | - --discoveryRefreshDelay 83 | - 1s 84 | - --zipkinAddress 85 | - zipkin.istio-system:9411 86 | - --connectTimeout 87 | - 10s 88 | - --statsdUdpAddress 89 | - istio-mixer.istio-system:9125 90 | - --proxyAdminPort 91 | - "15000" 92 | - --controlPlaneAuthPolicy 93 | - NONE 94 | env: 95 | - name: POD_NAME 96 | valueFrom: 97 | fieldRef: 98 | fieldPath: metadata.name 99 | - name: POD_NAMESPACE 100 | valueFrom: 101 | fieldRef: 102 | fieldPath: metadata.namespace 103 | - name: INSTANCE_IP 104 | valueFrom: 105 | fieldRef: 106 | fieldPath: status.podIP 107 | image: docker.io/istio/proxy:0.5.0 108 | imagePullPolicy: IfNotPresent 109 | name: istio-proxy 110 | resources: {} 111 | securityContext: 112 | privileged: true 113 | readOnlyRootFilesystem: false 114 | runAsUser: 1337 115 | volumeMounts: 116 | - mountPath: /etc/istio/proxy 117 | name: istio-envoy 118 | - mountPath: /etc/certs/ 119 | name: istio-certs 120 | readOnly: true 121 | 122 | volumes: 123 | - name: keystore-volume 124 | emptyDir: {} 125 | - name: istio-envoy 126 | emptyDir: 127 | medium: Memory 128 | sizeLimit: "0" 129 | - name: istio-certs 130 | secret: 131 | optional: true 132 | secretName: istio.default -------------------------------------------------------------------------------- /spring/product-catalog/src/main/fabric8/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: product-catalog 5 | annotations: 6 | auth.istio.io/8080: MUTUAL_TLS 7 | io.raffa.microsegmentation: 'true' 8 | io.raffa.microsegmentation.additional-ports: 15000/tcp 9 | spec: 10 | ports: 11 | - port: 8080 12 | protocol: TCP 13 | targetPort: 8080 14 | selector: 15 | app: product-catalog -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/ProductCatalogApplication.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductCatalogApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductCatalogApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/controllers/ProductCatalogController.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.DeleteMapping; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.PutMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestHeader; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.ResponseStatus; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import com.redhat.productcatalog.entities.Product; 18 | import com.redhat.productcatalog.services.ProductCatalogService; 19 | import java.util.Enumeration; 20 | import javax.servlet.http.HttpServletRequest; 21 | 22 | @RestController 23 | @RequestMapping(value = "/product") 24 | public class ProductCatalogController { 25 | 26 | @Autowired 27 | ProductCatalogService productService; 28 | 29 | @ResponseStatus(HttpStatus.CREATED) 30 | @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 31 | public Product saveProduct(@RequestBody Product product) { 32 | return productService.addProduct(product); 33 | } 34 | 35 | @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) 36 | public Iterable showProductList() { 37 | return productService.showProductList(); 38 | } 39 | 40 | @GetMapping(value="/{itemId}", produces = MediaType.APPLICATION_JSON_VALUE) 41 | public Product getProduct(@PathVariable("itemId") long itemId, HttpServletRequest request 42 | // TODO: This header is seemingly not being forwarded from Istio as specified in product-catalog-auth-policy (forward_jwt: true) 43 | // If the header is not being forwarded, it will result in a HTTP 400 error. 44 | // Commenting this out to prevent error until resolution is found (https://github.com/istio/proxy/issues/986) 45 | // This token is not needed at this point unless there will be another service call a token will be needed. 46 | //, @RequestHeader("Authorization") String token 47 | ) { 48 | 49 | Enumeration headerNames = request.getHeaderNames(); 50 | 51 | // Print out headers for debug (see comments above) 52 | while (headerNames.hasMoreElements()) { 53 | String headerName = headerNames.nextElement(); 54 | System.out.print("Header Name: " + headerName); 55 | String headerValue = request.getHeader(headerName); 56 | System.out.print(" Header Value: " + headerValue); 57 | System.out.println("\n"); 58 | } 59 | 60 | return productService.getProduct(itemId); 61 | } 62 | 63 | 64 | @PutMapping(value="/{itemId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 65 | @ResponseStatus(HttpStatus.OK) 66 | public Product updateProduct(@PathVariable("itemId") long itemId, @RequestBody Product product) { 67 | return productService.updateProduct(itemId, product); 68 | } 69 | 70 | @ResponseStatus(HttpStatus.NO_CONTENT) 71 | @DeleteMapping(value="/{itemId}") 72 | public void deleteProduct(@PathVariable("itemId") long itemId) { 73 | productService.deleteProduct(itemId); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/entities/Product.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog.entities; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | @Entity 10 | public class Product { 11 | 12 | @Id 13 | @GeneratedValue(strategy=GenerationType.IDENTITY) 14 | private Long itemId; 15 | 16 | 17 | private String name; 18 | 19 | @Column(length=2000) 20 | private String description; 21 | private double price; 22 | 23 | public Product() {} 24 | 25 | public Long getItemId() { 26 | return itemId; 27 | } 28 | 29 | public void setItemId(Long itemId) { 30 | this.itemId = itemId; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | 42 | public String getDescription() { 43 | return description; 44 | } 45 | 46 | public void setDescription(String description) { 47 | this.description = description; 48 | } 49 | 50 | public double getPrice() { 51 | return price; 52 | } 53 | 54 | public void setPrice(double price) { 55 | this.price = price; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/interfaces/ProductCatalog.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog.interfaces; 2 | 3 | import java.util.List; 4 | 5 | import com.redhat.productcatalog.entities.Product; 6 | 7 | public interface ProductCatalog { 8 | 9 | /** 10 | * @return 11 | */ 12 | List showProductList(); 13 | 14 | /** 15 | * @param itemId 16 | */ 17 | void deleteProduct(long itemId); 18 | 19 | /** 20 | * @param itemId 21 | * @return 22 | */ 23 | Product getProduct(long itemId); 24 | 25 | /** 26 | * @param product 27 | * @return 28 | */ 29 | Product addProduct(Product product); 30 | 31 | /** 32 | * @param itemId 33 | * @param product 34 | * @return 35 | */ 36 | Product updateProduct(long itemId, Product product); 37 | } 38 | -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.redhat.productcatalog.entities.Product; 6 | 7 | public interface ProductRepository extends JpaRepository{ 8 | 9 | public Product findByName(String name); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /spring/product-catalog/src/main/java/com/redhat/productcatalog/services/ProductCatalogService.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productcatalog.services; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.redhat.productcatalog.entities.Product; 10 | import com.redhat.productcatalog.interfaces.ProductCatalog; 11 | import com.redhat.productcatalog.repositories.ProductRepository; 12 | 13 | @Service 14 | public class ProductCatalogService implements ProductCatalog { 15 | 16 | @Autowired 17 | ProductRepository repository; 18 | 19 | @Override 20 | public Product addProduct(Product product) { 21 | verifyCorrectPayload(product); 22 | return repository.save(product); 23 | } 24 | 25 | @Override 26 | public List showProductList() { 27 | return repository.findAll(); 28 | } 29 | 30 | @Override 31 | public Product getProduct(long itemId) { 32 | verifyProductExists(itemId); 33 | return repository.findOne(itemId); 34 | } 35 | 36 | @Override 37 | public Product updateProduct(long itemId, Product product) { 38 | verifyProductExists(itemId); 39 | verifyCorrectPayload(product); 40 | product.setItemId(itemId); 41 | return repository.save(product); 42 | 43 | } 44 | 45 | @Override 46 | public void deleteProduct(long itemId) { 47 | verifyProductExists(itemId); 48 | repository.delete(itemId); 49 | } 50 | private void verifyProductExists(long itemId) { 51 | if (!repository.exists(itemId)) { 52 | throw new RuntimeException(String.format("Product with id=%d was not found", itemId)); 53 | } 54 | } 55 | 56 | private void verifyCorrectPayload(Product product) { 57 | if (Objects.isNull(product) || product.getName().isEmpty()) { 58 | throw new RuntimeException("Product details can't be empty"); 59 | } 60 | 61 | if (!Objects.isNull(product.getItemId())) { 62 | throw new RuntimeException("Id field must be generated"); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /spring/product-catalog/src/main/resources/application-openshift.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://postgres-ha.pgo.svc.cluster.local:5432/userdb?sslmode=disable 2 | #spring.datasource.username=postgres 3 | #spring.datasource.password=admin 4 | spring.datasource.driver-class-name=org.postgresql.Driver -------------------------------------------------------------------------------- /spring/product-catalog/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # H2 Database settings 2 | spring.datasource.url=jdbc:h2:mem:products;DB_CLOSE_ON_EXIT=FALSE 3 | spring.datasource.username=sa 4 | spring.datasource.password= 5 | spring.datasource.driver-class-name=org.h2.Driver 6 | server.port=8080 7 | spring.application.name=product-catalog -------------------------------------------------------------------------------- /spring/product-catalog/src/main/resources/bootstrap-openshift.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: product-catalog 2 | spring.cloud.vault: 3 | host: vault.vault.svc 4 | port: 8200 5 | scheme: https 6 | authentication: KUBERNETES 7 | kubernetes: 8 | role: product-catalog 9 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 10 | config: 11 | order: -10 12 | generic: 13 | enabled: false 14 | postgresql: 15 | enabled: true 16 | role: pg-readwrite 17 | backend: database 18 | username-property: spring.datasource.username 19 | password-property: spring.datasource.password -------------------------------------------------------------------------------- /spring/product-catalog/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Product Catalog CRUD - Spring Boot 6 | 7 | 22 | 23 | 24 | 121 | 122 | 123 | 124 |
125 |

Product Catalog CRUD - Spring Boot

126 |

127 | This application demonstrates how a Spring Boot application implements a CRUD endpoint to manage products. 128 | This management interface invokes the CRUD service endpoint, that interact with a PosgreSQL database using JPA. 129 |

130 | 131 |

Add/Edit a product

132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | 146 |
147 | 148 |

Product List

149 |
150 |
Name
151 |
152 |
153 |
{{ product.itemId }}
154 |
{{ product.name }}
155 |
{{ product.description }}
156 |
${{ product.price }}
157 |
Edit Remove 158 |
159 |
160 |
161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /spring/product-catalog/src/test/java/com/redhat/productcatalog/ProductCatalogServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.redhat.coolstore.productcatalog; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import com.redhat.productcatalog.ProductCatalogApplication; 15 | import com.redhat.productcatalog.entities.Product; 16 | import com.redhat.productcatalog.services.ProductCatalogService; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest(classes={ProductCatalogApplication.class}) 20 | //@DataJpaTest 21 | public class ProductCatalogServiceTests { 22 | 23 | @Autowired 24 | ProductCatalogService catalog; 25 | 26 | 27 | @Test 28 | public void testFindAll() { 29 | List productList = catalog.showProductList(); 30 | assertEquals(productList.size(), 8); 31 | } 32 | 33 | @Test(expected=RuntimeException.class) 34 | public void testSaveAndDeleteProduct() { 35 | 36 | Product newProduct = new Product(); 37 | newProduct.setName("Test Prod"); 38 | newProduct.setDescription("This is a description"); 39 | newProduct.setPrice(10.00d); 40 | 41 | Product product = catalog.addProduct(newProduct); 42 | long id = product.getItemId(); 43 | 44 | assertNotNull(catalog.getProduct(id)); 45 | 46 | catalog.deleteProduct(id); 47 | 48 | catalog.getProduct(id); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /spring/product-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /spring/product-frontend/README.adoc: -------------------------------------------------------------------------------- 1 | == CRUD - Spring Boot - Sample Product inventroy 2 | 3 | This application requires Java 8 JDK or greater and Maven 3.3.x or greater. 4 | 5 | This application requires the product inventory application to be deployed and running as it makes rest calls to it to get the inventory records from a secured endpoint. 6 | 7 | == Building, Deploying and Running the Application 8 | 9 | Update `src/main/resources/static/keycloak.json` with your client secret from SSO. 10 | 11 | To deploy your application: 12 | [source,bash,options="nowrap",subs="attributes+"] 13 | ---- 14 | $ oc project product 15 | 16 | $ mvn clean fabric8:deploy -Popenshift -DSSO_AUTH_SERVER_URL=$(oc get route secure-sso -n rh-sso -o jsonpath='{"https://"}{.spec.host}{"/auth"}') -DPRODUCT_INVENTORY_SERVICE_URL=$(oc get route product-inventory -o jsonpath='{"http://"}{.spec.host}') 17 | ---- 18 | 19 | == Interacting with the Application 20 | 21 | To interact with the application, you first need to obtain it's URL: 22 | 23 | [source,bash,options="nowrap",subs="attributes+"] 24 | ---- 25 | $ oc get route product-frontend -o jsonpath={$.spec.host} 26 | ---- 27 | 28 | Open this link in the browser to access the UI 29 | -------------------------------------------------------------------------------- /spring/product-frontend/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /spring/product-frontend/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /spring/product-frontend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.redhat 7 | product-frontend 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | product-frontend 12 | Demo project for Spring Boot 13 | 14 | 15 | io.openshift 16 | booster-parent 17 | 11 18 | 19 | 20 | 21 | 1.5.8.RELEASE 22 | 1.5.8.Final 23 | 24 | 25 | 26 | 27 | 28 | me.snowdrop 29 | spring-boot-bom 30 | ${spring-boot.bom.version} 31 | pom 32 | import 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-actuator 52 | 53 | 54 | 55 | 56 | 57 | 58 | src/main/fabric8 59 | true 60 | 61 | 62 | src/main/resources 63 | true 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | 73 | 74 | 75 | 76 | 77 | local 78 | 79 | true 80 | 81 | 82 | 83 | openshift 84 | 85 | 86 | 87 | io.fabric8 88 | fabric8-maven-plugin 89 | 90 | 91 | fmp 92 | package 93 | 94 | resource 95 | build 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | openshift-it 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-failsafe-plugin 110 | 111 | 112 | 113 | integration-test 114 | verify 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | io.fabric8 125 | kubernetes-client 126 | 1.4.34 127 | test 128 | 129 | 130 | io.fabric8 131 | openshift-client 132 | 1.4.34 133 | test 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /spring/product-frontend/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - env: 6 | - name: JAVA_APP_DIR 7 | value: /deployments 8 | 9 | -------------------------------------------------------------------------------- /spring/product-frontend/src/main/fabric8/route.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Route 3 | metadata: 4 | name: ${project.artifactId} 5 | spec: 6 | port: 7 | targetPort: 8070 8 | to: 9 | kind: Service 10 | name: ${project.artifactId} 11 | tls: 12 | termination: edge -------------------------------------------------------------------------------- /spring/product-frontend/src/main/java/com/redhat/productfrontend/ProductFrontendApplication.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productfrontend; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductFrontendApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductFrontendApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring/product-frontend/src/main/resources/application-openshift.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=product-frontend 2 | server.port = 8070 -------------------------------------------------------------------------------- /spring/product-frontend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=product-frontend 2 | server.port = 8070 -------------------------------------------------------------------------------- /spring/product-frontend/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Secured HTTP API 8 | 9 | 10 | 11 |
12 | 13 |
14 |

Secure Product Frontend API - Spring Boot

15 |

The application provides a secure a REST endpoint using Red Hat SSO. 16 | Red Hat SSO implements the OAuth 2.0 specification and uses it to issue access tokens to provide clients with various access rights to secured resources. 17 | Securing an application with SSO enables you to add security to your applications while centralizing the security configuration. 18 |

19 | 20 |

Using the Product Inventory service

21 |

The Product Inventory service is a protected endpoint. You will need to login first. You will be redirected to the RH-SSO login screen, after successful 22 | login you will be redirected back here. Then use the invoke button to access the secure endpoint. 23 |

24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 |

Product Frontend service (as Unauthenticated):

32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 |

Result:

40 |
Invoke the service to see the result.
41 | 42 |

Curl command for the command line:

43 |
44 | 45 |
46 |
47 |
48 | 49 | 50 | 53 | 54 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /spring/product-frontend/src/main/resources/static/js/keycloak-3.1.0.min.js: -------------------------------------------------------------------------------- 1 | (function(a,c){var b=function(z){if(!(this instanceof b)){return new b(z)}var h=this;var p;var u=[];var f;var v={enable:true,callbackList:[],interval:5};h.init=function(E){h.authenticated=false;f=q();if(E&&E.adapter==="cordova"){p=x("cordova")}else{if(E&&E.adapter==="default"){p=x()}else{if(a.Cordova){p=x("cordova")}else{p=x()}}}if(E){if(typeof E.checkLoginIframe!=="undefined"){v.enable=E.checkLoginIframe}if(E.checkLoginIframeInterval){v.interval=E.checkLoginIframeInterval}if(E.onLoad==="login-required"){h.loginRequired=true}if(E.responseMode){if(E.responseMode==="query"||E.responseMode==="fragment"){h.responseMode=E.responseMode}else{throw"Invalid value for responseMode"}}if(E.flow){switch(E.flow){case"standard":h.responseType="code";break;case"implicit":h.responseType="id_token token";break;case"hybrid":h.responseType="code id_token token";break;default:throw"Invalid value for flow"}h.flow=E.flow}if(E.timeSkew!=null){h.timeSkew=E.timeSkew}}if(!h.responseMode){h.responseMode="fragment"}if(!h.responseType){h.responseType="code";h.flow="standard"}var F=d();var B=d();B.promise.success(function(){h.onReady&&h.onReady(h.authenticated);F.setSuccess(h.authenticated)}).error(function(G){F.setError(G)});var D=j(z);function C(){var H=function(I){if(!I){G.prompt="none"}h.login(G).success(function(){B.setSuccess()}).error(function(){B.setError()})};var G={};switch(E.onLoad){case"check-sso":if(v.enable){y().success(function(){l().success(function(){H(false)}).error(function(){B.setSuccess()})})}else{H(false)}break;case"login-required":H(true);break;default:throw"Invalid value for onLoad"}}function A(){var G=g(a.location.href);if(G){y();a.history.replaceState({},null,G.newUrl);n(G,B);return}else{if(E){if(E.token&&E.refreshToken){t(E.token,E.refreshToken,E.idToken);if(v.enable){y().success(function(){l().success(function(){h.onAuthSuccess&&h.onAuthSuccess();B.setSuccess()}).error(function(){t(null,null,null);B.setSuccess()})})}else{h.updateToken(-1).success(function(){h.onAuthSuccess&&h.onAuthSuccess();B.setSuccess()}).error(function(){h.onAuthError&&h.onAuthError();if(E.onLoad){C()}else{B.setError()}})}}else{if(E.onLoad){C()}else{B.setSuccess()}}}else{B.setSuccess()}}}D.success(A);D.error(function(){F.setError()});return F.promise};h.login=function(A){return p.login(A)};h.createLoginUrl=function(C){var G=e();var E=e();var H=p.redirectUri(C);var A={state:G,nonce:E,redirectUri:encodeURIComponent(H),};if(C&&C.prompt){A.prompt=C.prompt}f.add(A);var F="auth";if(C&&C.action=="register"){F="registrations"}var D=(C&&C.scope)?"openid "+C.scope:"openid";var B=o()+"/protocol/openid-connect/"+F+"?client_id="+encodeURIComponent(h.clientId)+"&redirect_uri="+encodeURIComponent(H)+"&state="+encodeURIComponent(G)+"&nonce="+encodeURIComponent(E)+"&response_mode="+encodeURIComponent(h.responseMode)+"&response_type="+encodeURIComponent(h.responseType)+"&scope="+encodeURIComponent(D);if(C&&C.prompt){B+="&prompt="+encodeURIComponent(C.prompt)}if(C&&C.maxAge){B+="&max_age="+encodeURIComponent(C.maxAge)}if(C&&C.loginHint){B+="&login_hint="+encodeURIComponent(C.loginHint)}if(C&&C.idpHint){B+="&kc_idp_hint="+encodeURIComponent(C.idpHint)}if(C&&C.locale){B+="&ui_locales="+encodeURIComponent(C.locale)}return B};h.logout=function(A){return p.logout(A)};h.createLogoutUrl=function(B){var A=o()+"/protocol/openid-connect/logout?redirect_uri="+encodeURIComponent(p.redirectUri(B,false));return A};h.register=function(A){return p.register(A)};h.createRegisterUrl=function(A){if(!A){A={}}A.action="register";return h.createLoginUrl(A)};h.createAccountUrl=function(B){var A=o()+"/account?referrer="+encodeURIComponent(h.clientId)+"&referrer_uri="+encodeURIComponent(p.redirectUri(B));return A};h.accountManagement=function(){return p.accountManagement()};h.hasRealmRole=function(B){var A=h.realmAccess;return !!A&&A.roles.indexOf(B)>=0};h.hasResourceRole=function(C,B){if(!h.resourceAccess){return false}var A=h.resourceAccess[B||h.clientId];return !!A&&A.roles.indexOf(C)>=0};h.loadUserProfile=function(){var A=o()+"/account";var B=new XMLHttpRequest();B.open("GET",A,true);B.setRequestHeader("Accept","application/json");B.setRequestHeader("Authorization","bearer "+h.token);var C=d();B.onreadystatechange=function(){if(B.readyState==4){if(B.status==200){h.profile=JSON.parse(B.responseText);C.setSuccess(h.profile)}else{C.setError()}}};B.send();return C.promise};h.loadUserInfo=function(){var A=o()+"/protocol/openid-connect/userinfo";var B=new XMLHttpRequest();B.open("GET",A,true);B.setRequestHeader("Accept","application/json");B.setRequestHeader("Authorization","bearer "+h.token);var C=d();B.onreadystatechange=function(){if(B.readyState==4){if(B.status==200){h.userInfo=JSON.parse(B.responseText);C.setSuccess(h.userInfo)}else{C.setError()}}};B.send();return C.promise};h.isTokenExpired=function(A){if(!h.tokenParsed||(!h.refreshToken&&h.flow!="implicit")){throw"Not authenticated"}if(h.timeSkew==null){console.info("[KEYCLOAK] Unable to determine if token is expired as timeskew is not set");return true}var B=h.tokenParsed.exp-Math.ceil(new Date().getTime()/1000)+h.timeSkew;if(A){B-=A}return B<0};h.updateToken=function(A){var D=d();if(!h.refreshToken){D.setError();return D.promise}A=A||5;var B=function(){var G=false;if(A==-1){G=true;console.info("[KEYCLOAK] Refreshing token: forced refresh")}else{if(!h.tokenParsed||h.isTokenExpired(A)){G=true;console.info("[KEYCLOAK] Refreshing token: token expired")}}if(!G){D.setSuccess(false)}else{var I="grant_type=refresh_token&refresh_token="+h.refreshToken;var F=o()+"/protocol/openid-connect/token";u.push(D);if(u.length==1){var H=new XMLHttpRequest();H.open("POST",F,true);H.setRequestHeader("Content-type","application/x-www-form-urlencoded");H.withCredentials=true;if(h.clientId&&h.clientSecret){H.setRequestHeader("Authorization","Basic "+btoa(h.clientId+":"+h.clientSecret))}else{I+="&client_id="+encodeURIComponent(h.clientId)}var E=new Date().getTime();H.onreadystatechange=function(){if(H.readyState==4){if(H.status==200){console.info("[KEYCLOAK] Token refreshed");E=(E+new Date().getTime())/2;var K=JSON.parse(H.responseText);t(K.access_token,K.refresh_token,K.id_token,E);h.onAuthRefreshSuccess&&h.onAuthRefreshSuccess();for(var J=u.pop();J!=null;J=u.pop()){J.setSuccess(true)}}else{console.warn("[KEYCLOAK] Failed to refresh token");h.onAuthRefreshError&&h.onAuthRefreshError();for(var J=u.pop();J!=null;J=u.pop()){J.setError(true)}}}};H.send(I)}}};if(v.enable){var C=l();C.success(function(){B()}).error(function(){D.setError()})}else{B()}return D.promise};h.clearToken=function(){if(h.token){t(null,null,null);h.onAuthLogout&&h.onAuthLogout();if(h.loginRequired){h.login()}}};function o(){if(h.authServerUrl.charAt(h.authServerUrl.length-1)=="/"){return h.authServerUrl+"realms/"+encodeURIComponent(h.realm)}else{return h.authServerUrl+"/realms/"+encodeURIComponent(h.realm)}}function w(){if(!a.location.origin){return a.location.protocol+"//"+a.location.hostname+(a.location.port?":"+a.location.port:"")}else{return a.location.origin}}function n(H,K){var D=H.code;var I=H.error;var E=H.prompt;var C=new Date().getTime();if(I){if(E!="none"){var B={error:I,error_description:H.error_description};h.onAuthError&&h.onAuthError(B);K&&K.setError(B)}else{K&&K.setSuccess()}return}else{if((h.flow!="standard")&&(H.access_token||H.id_token)){G(H.access_token,null,H.id_token,true)}}if((h.flow!="implicit")&&D){var F="code="+D+"&grant_type=authorization_code";var A=o()+"/protocol/openid-connect/token";var J=new XMLHttpRequest();J.open("POST",A,true);J.setRequestHeader("Content-type","application/x-www-form-urlencoded");if(h.clientId&&h.clientSecret){J.setRequestHeader("Authorization","Basic "+btoa(h.clientId+":"+h.clientSecret))}else{F+="&client_id="+encodeURIComponent(h.clientId)}F+="&redirect_uri="+H.redirectUri;J.withCredentials=true;J.onreadystatechange=function(){if(J.readyState==4){if(J.status==200){var L=JSON.parse(J.responseText);G(L.access_token,L.refresh_token,L.id_token,h.flow==="standard")}else{h.onAuthError&&h.onAuthError();K&&K.setError()}}};J.send(F)}function G(L,M,O,N){C=(C+new Date().getTime())/2;t(L,M,O,C);if((h.tokenParsed&&h.tokenParsed.nonce!=H.storedNonce)||(h.refreshTokenParsed&&h.refreshTokenParsed.nonce!=H.storedNonce)||(h.idTokenParsed&&h.idTokenParsed.nonce!=H.storedNonce)){console.info("[KEYCLOAK] Invalid nonce, clearing token");h.clearToken();K&&K.setError()}else{if(N){h.onAuthSuccess&&h.onAuthSuccess();K&&K.setSuccess()}}}}function j(C){var F=d();var B;if(!z){B="keycloak.json"}else{if(typeof z==="string"){B=z}}if(B){var E=new XMLHttpRequest();E.open("GET",B,true);E.setRequestHeader("Accept","application/json");E.onreadystatechange=function(){if(E.readyState==4){if(E.status==200){var G=JSON.parse(E.responseText);h.authServerUrl=G["auth-server-url"];h.realm=G.realm;h.clientId=G.resource;h.clientSecret=(G.credentials||{})["secret"];F.setSuccess()}else{F.setError()}}};E.send()}else{if(!z.url){var A=document.getElementsByTagName("script");for(var D=0;D";return A}function g(B){var A=new k(B,h.responseMode).parseUri();var C=f.get(A.state);if(C&&(A.code||A.error||A.access_token||A.id_token)){A.redirectUri=C.redirectUri;A.storedNonce=C.nonce;A.prompt=C.prompt;if(A.fragment){A.newUrl+="#"+A.fragment}return A}}function d(){var A={setSuccess:function(B){A.success=true;A.result=B;if(A.successCallback){A.successCallback(B)}},setError:function(B){A.error=true;A.result=B;if(A.errorCallback){A.errorCallback(B)}},promise:{success:function(B){if(A.success){B(A.result)}else{if(!A.error){A.successCallback=B}}return A.promise},error:function(B){if(A.error){B(A.result)}else{if(!A.success){A.errorCallback=B}}return A.promise}}};return A}function y(){var E=d();if(!v.enable){E.setSuccess();return E.promise}if(v.iframe){E.setSuccess();return E.promise}var C=document.createElement("iframe");v.iframe=C;C.onload=function(){var F=o();if(F.charAt(0)==="/"){v.iframeOrigin=w()}else{v.iframeOrigin=F.substring(0,F.indexOf("/",8))}E.setSuccess();setTimeout(A,v.interval*1000)};var D=o()+"/protocol/openid-connect/login-status-iframe.html";C.setAttribute("src",D);C.style.display="none";document.body.appendChild(C);var B=function(H){if((H.origin!==v.iframeOrigin)||(v.iframe.contentWindow!==H.source)){return}if(!(H.data=="unchanged"||H.data=="changed"||H.data=="error")){return}if(H.data!="unchanged"){h.clearToken()}var G=v.callbackList.splice(0,v.callbackList.length);for(var F=G.length-1;F>=0;--F){var I=G[F];if(H.data=="unchanged"){I.setSuccess()}else{I.setError()}}};a.addEventListener("message",B,false);var A=function(){l();if(h.token){setTimeout(A,v.interval*1000)}};return E.promise}function l(){var C=d();if(v.iframe&&v.iframeOrigin){var B=h.clientId+" "+h.sessionId;v.callbackList.push(C);var A=v.iframeOrigin;if(v.callbackList.length==1){v.iframe.contentWindow.postMessage(B,A)}}else{C.setSuccess()}return C.promise}function x(A){if(!A||A=="default"){return{login:function(B){a.location.href=h.createLoginUrl(B);return d().promise},logout:function(B){a.location.href=h.createLogoutUrl(B);return d().promise},register:function(B){a.location.href=h.createRegisterUrl(B);return d().promise},accountManagement:function(){a.location.href=h.createAccountUrl();return d().promise},redirectUri:function(B,C){if(arguments.length==1){C=true}if(B&&B.redirectUri){return B.redirectUri}else{if(h.redirectUri){return h.redirectUri}else{var D=location.href;if(location.hash&&C){D=D.substring(0,location.href.indexOf("#"));D+=(D.indexOf("?")==-1?"?":"&")+"redirect_fragment="+encodeURIComponent(location.hash.substring(1))}return D}}}}}if(A=="cordova"){v.enable=false;return{login:function(B){var G=d();var F="location=no";if(B&&B.prompt=="none"){F+=",hidden=yes"}var E=h.createLoginUrl(B);var D=a.open(E,"_blank",F);var C=false;D.addEventListener("loadstart",function(H){if(H.url.indexOf("http://localhost")==0){var I=g(H.url);n(I,G);D.close();C=true}});D.addEventListener("loaderror",function(H){if(!C){if(H.url.indexOf("http://localhost")==0){var I=g(H.url);n(I,G);D.close();C=true}else{G.setError();D.close()}}});return G.promise},logout:function(D){var F=d();var B=h.createLogoutUrl(D);var E=a.open(B,"_blank","location=no,hidden=yes");var C;E.addEventListener("loadstart",function(G){if(G.url.indexOf("http://localhost")==0){E.close()}});E.addEventListener("loaderror",function(G){if(G.url.indexOf("http://localhost")==0){E.close()}else{C=true;E.close()}});E.addEventListener("exit",function(G){if(C){F.setError()}else{h.clearToken();F.setSuccess()}});return F.promise},register:function(){var B=h.createRegisterUrl();var C=a.open(B,"_blank","location=no");C.addEventListener("loadstart",function(D){if(D.url.indexOf("http://localhost")==0){C.close()}})},accountManagement:function(){var B=h.createAccountUrl();var C=a.open(B,"_blank","location=no");C.addEventListener("loadstart",function(D){if(D.url.indexOf("http://localhost")==0){C.close()}})},redirectUri:function(B){return"http://localhost"}}}throw"invalid adapter type: "+A}var m=function(){if(!(this instanceof m)){return new m()}localStorage.setItem("kc-test","test");localStorage.removeItem("kc-test");var A=this;function B(){var H=new Date().getTime();for(var E=0;E \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /spring/product-inventory/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /spring/product-inventory/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.redhat 7 | product-inventory 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | product-inventory 12 | Product Inventory communicates with Product Catalog 13 | 14 | 15 | io.openshift 16 | booster-parent 17 | 11 18 | 19 | 20 | 21 | 1.5.8.RELEASE 22 | 9.4.1212 23 | 1.5.8.Final 24 | 25 | 26 | 27 | 28 | 29 | me.snowdrop 30 | spring-boot-bom 31 | ${spring-boot.bom.version} 32 | pom 33 | import 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-vault-dependencies 38 | 1.1.0.RELEASE 39 | import 40 | pom 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-web 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-data-jpa 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-actuator 57 | 58 | 59 | 60 | org.keycloak 61 | keycloak-adapter-spi 62 | 63 | 64 | org.keycloak 65 | keycloak-tomcat8-adapter 66 | 67 | 68 | org.keycloak 69 | keycloak-spring-boot-adapter 70 | 71 | 72 | org.keycloak 73 | keycloak-authz-client 74 | 75 | 76 | com.google.guava 77 | guava 78 | 23.0 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | com.h2database 88 | h2 89 | test 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-maven-plugin 98 | ${maven.spring-boot.plugin.version} 99 | 100 | 101 | local 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | local 111 | 112 | true 113 | 114 | 115 | 116 | com.h2database 117 | h2 118 | runtime 119 | 120 | 121 | 122 | 123 | openshift 124 | 125 | 126 | org.postgresql 127 | postgresql 128 | ${postgresql.version} 129 | runtime 130 | 131 | 132 | 133 | org.springframework.cloud 134 | spring-cloud-starter-vault-config 135 | 136 | 137 | org.springframework.cloud 138 | spring-cloud-vault-config-databases 139 | 140 | 141 | 142 | 143 | 144 | org.springframework.boot 145 | spring-boot-maven-plugin 146 | 147 | 148 | 149 | repackage 150 | 151 | 152 | 153 | 154 | 155 | io.fabric8 156 | fabric8-maven-plugin 157 | 158 | 159 | fmp 160 | package 161 | 162 | resource 163 | build 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/fabric8/configmap.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Template 3 | metadata: 4 | name: inventory-sso-config 5 | objects: 6 | - kind: ConfigMap 7 | apiVersion: v1 8 | metadata: 9 | name: keycloak-properties 10 | data: 11 | realm: "master" 12 | realm-key: ${RH_SSO_REALM_KEY} 13 | resource: "inventoryservice" 14 | auth-server-url: https://${RH_SSO_URL}/auth 15 | credentails-secret: ${RH_SSO_CREDENTIAL_SECRET} 16 | parameters: 17 | - description: rh-ss realm key 18 | required: true 19 | name: RH_SSO_REALM_KEY 20 | - description: rh-sso service url 21 | required: true 22 | name: RH_SSO_URL 23 | - description: rh-sso credential secret 24 | required: true 25 | name: RH_SSO_CREDENTIAL_SECRET -------------------------------------------------------------------------------- /spring/product-inventory/src/main/fabric8/deploymentconfig.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: DeploymentConfig 3 | metadata: 4 | name: product-inventory 5 | spec: 6 | triggers: 7 | - 8 | type: ConfigChange 9 | - 10 | type: ImageChange 11 | imageChangeParams: 12 | automatic: true 13 | containerNames: 14 | - product-inventory 15 | from: 16 | kind: ImageStreamTag 17 | namespace: product 18 | name: 'product-inventory:latest' 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | app: product-inventory 24 | spec: 25 | initContainers: 26 | - name: pem-to-truststore 27 | image: registry.access.redhat.com/redhat-sso-7/sso71-openshift:1.1-16 28 | env: 29 | - name: ca_bundle 30 | value: /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt 31 | - name: truststore_jks 32 | value: /var/run/secrets/java.io/keystores/truststore.jks 33 | - name: password 34 | value: changeit 35 | command: ['/bin/bash'] 36 | args: ['-c', "csplit -z -f crt- $ca_bundle '/-----BEGIN CERTIFICATE-----/' '{*}' && for file in crt-*; do keytool -import -noprompt -keystore $truststore_jks -file $file -storepass changeit -alias service-$file; done"] 37 | volumeMounts: 38 | - name: keystore-volume 39 | mountPath: /var/run/secrets/java.io/keystores 40 | 41 | - args: 42 | - -p 43 | - "15001" 44 | - -u 45 | - "1337" 46 | image: docker.io/istio/proxy_init:0.5.0 47 | imagePullPolicy: IfNotPresent 48 | name: istio-init 49 | resources: {} 50 | securityContext: 51 | capabilities: 52 | add: 53 | - NET_ADMIN 54 | privileged: true 55 | containers: 56 | - name: product-inventory 57 | image: "product/product-inventory:latest" 58 | imagePullPolicy: Always 59 | env: 60 | - name: JAVA_OPTIONS 61 | value: -Dspring.profiles.active=openshift -Djavax.net.ssl.trustStore=/var/run/secrets/java.io/keystores/truststore.jks -Djavax.net.ssl.trustStorePassword=changeit 62 | - name: SSO_REALM 63 | valueFrom: 64 | configMapKeyRef: 65 | name: keycloak-properties 66 | key: realm 67 | - name: SSO_RESOURCE 68 | valueFrom: 69 | configMapKeyRef: 70 | name: keycloak-properties 71 | key: resource 72 | - name: SSO_REALM_KEY 73 | valueFrom: 74 | configMapKeyRef: 75 | name: keycloak-properties 76 | key: realm-key 77 | - name: SSO_AUTH_SERVER_URL 78 | valueFrom: 79 | configMapKeyRef: 80 | name: keycloak-properties 81 | key: auth-server-url 82 | - name: SSO_CREDENTIALS_SECRET 83 | valueFrom: 84 | configMapKeyRef: 85 | name: keycloak-properties 86 | key: credentails-secret 87 | volumeMounts: 88 | - name: keystore-volume 89 | mountPath: /var/run/secrets/java.io/keystores 90 | 91 | - args: 92 | - proxy 93 | - sidecar 94 | - -v 95 | - "2" 96 | - --configPath 97 | - /etc/istio/proxy 98 | - --binaryPath 99 | - /usr/local/bin/envoy 100 | - --serviceCluster 101 | - product-inventory 102 | - --drainDuration 103 | - 45s 104 | - --parentShutdownDuration 105 | - 1m0s 106 | - --discoveryAddress 107 | - istio-pilot.istio-system:15003 108 | - --discoveryRefreshDelay 109 | - 1s 110 | - --zipkinAddress 111 | - zipkin.istio-system:9411 112 | - --connectTimeout 113 | - 10s 114 | - --statsdUdpAddress 115 | - istio-mixer.istio-system:9125 116 | - --proxyAdminPort 117 | - "15000" 118 | - --controlPlaneAuthPolicy 119 | - NONE 120 | env: 121 | - name: POD_NAME 122 | valueFrom: 123 | fieldRef: 124 | fieldPath: metadata.name 125 | - name: POD_NAMESPACE 126 | valueFrom: 127 | fieldRef: 128 | fieldPath: metadata.namespace 129 | - name: INSTANCE_IP 130 | valueFrom: 131 | fieldRef: 132 | fieldPath: status.podIP 133 | image: docker.io/istio/proxy:0.5.0 134 | imagePullPolicy: IfNotPresent 135 | name: istio-proxy 136 | resources: {} 137 | securityContext: 138 | privileged: true 139 | readOnlyRootFilesystem: false 140 | runAsUser: 1337 141 | volumeMounts: 142 | - mountPath: /etc/istio/proxy 143 | name: istio-envoy 144 | - mountPath: /etc/certs/ 145 | name: istio-certs 146 | readOnly: true 147 | 148 | volumes: 149 | - name: keystore-volume 150 | emptyDir: {} 151 | - name: istio-envoy 152 | emptyDir: 153 | medium: Memory 154 | sizeLimit: "0" 155 | - name: istio-certs 156 | secret: 157 | optional: true 158 | secretName: istio.default -------------------------------------------------------------------------------- /spring/product-inventory/src/main/fabric8/route.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Route 3 | metadata: 4 | name: product-inventory 5 | spec: 6 | port: 7 | targetPort: 8090 8 | to: 9 | kind: Service 10 | name: product-inventory 11 | tls: 12 | termination: edge -------------------------------------------------------------------------------- /spring/product-inventory/src/main/fabric8/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: product-inventory 5 | annotations: 6 | io.raffa.microsegmentation: 'true' 7 | io.raffa.microsegmentation.additional-ports: 15000/tcp 8 | spec: 9 | ports: 10 | - port: 8090 11 | protocol: TCP 12 | targetPort: 8090 13 | selector: 14 | app: product-inventory -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/HttpHeaderForwarderClientHttpRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory; 2 | 3 | import org.springframework.http.HttpRequest; 4 | import org.springframework.http.client.ClientHttpRequestExecution; 5 | import org.springframework.http.client.ClientHttpRequestInterceptor; 6 | import org.springframework.http.client.ClientHttpResponse; 7 | 8 | import java.io.IOException; 9 | 10 | public class HttpHeaderForwarderClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { 11 | 12 | @Override 13 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) 14 | throws IOException { 15 | request.getHeaders().putAll(HttpHeaderForwarderHandlerInterceptor.getHeaders()); 16 | return execution.execute(request, body); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/HttpHeaderForwarderHandlerInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | import org.springframework.web.servlet.ModelAndView; 14 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 15 | 16 | import com.google.common.collect.ImmutableSet; 17 | 18 | public class HttpHeaderForwarderHandlerInterceptor extends HandlerInterceptorAdapter { 19 | 20 | private static final ThreadLocal>> HEADERS_THREAD_LOCAL = new ThreadLocal<>(); 21 | 22 | private static final Set FORWARDED_HEADER_NAMES = ImmutableSet.of( 23 | "x-request-id", 24 | "x-b3-traceid", 25 | "x-b3-spanid", 26 | "x-b3-parentspanid", 27 | "x-b3-sampled", 28 | "x-b3-flags", 29 | "x-ot-span-context", 30 | "user-agent" 31 | ); 32 | 33 | @Override 34 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 35 | Map> headerMap = Collections.list(request.getHeaderNames()).stream() 36 | .map(String::toLowerCase) 37 | .filter(FORWARDED_HEADER_NAMES::contains) 38 | .collect(Collectors.toMap( 39 | Function.identity(), 40 | h -> Collections.list(request.getHeaders(h)) 41 | )); 42 | HEADERS_THREAD_LOCAL.set(headerMap); 43 | return super.preHandle(request, response, handler); 44 | } 45 | 46 | @Override 47 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 48 | HEADERS_THREAD_LOCAL.remove(); 49 | } 50 | 51 | static Map> getHeaders() { 52 | return HEADERS_THREAD_LOCAL.get(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/ProductInventoryApplication.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory; 2 | 3 | import java.util.Collections; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.client.RestTemplate; 9 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 11 | 12 | @SpringBootApplication 13 | public class ProductInventoryApplication extends WebMvcConfigurerAdapter { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(ProductInventoryApplication.class, args); 17 | } 18 | 19 | @Override 20 | public void addInterceptors(InterceptorRegistry registry) { 21 | registry.addInterceptor(new HttpHeaderForwarderHandlerInterceptor()); 22 | } 23 | 24 | @Bean 25 | public RestTemplate restTemplate() { 26 | RestTemplate restTemplate = new RestTemplate(); 27 | restTemplate.setInterceptors(Collections.singletonList(new HttpHeaderForwarderClientHttpRequestInterceptor())); 28 | return restTemplate; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/controllers/ProductInventoryController.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.controllers; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.PutMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestHeader; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.ResponseStatus; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import com.fasterxml.jackson.core.JsonProcessingException; 21 | import com.redhat.productinventory.dtos.InventoryDTO; 22 | import com.redhat.productinventory.entities.Inventory; 23 | import com.redhat.productinventory.services.ProductInventoryService; 24 | 25 | @RestController 26 | @RequestMapping(value = "/inventory") 27 | public class ProductInventoryController { 28 | 29 | @Autowired 30 | ProductInventoryService inventoryService; 31 | 32 | @ResponseStatus(HttpStatus.CREATED) 33 | @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 34 | public Inventory saveProduct(@RequestBody Inventory inventory) { 35 | return inventoryService.addInventory(inventory); 36 | } 37 | 38 | @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) 39 | public List showProductList(@RequestHeader("Authorization") String token) throws JsonProcessingException, IOException { 40 | return inventoryService.showInventoryList(token); 41 | } 42 | 43 | @GetMapping(value="/{invId}", produces = MediaType.APPLICATION_JSON_VALUE) 44 | public InventoryDTO getProduct(@PathVariable("invId") long invId) throws JsonProcessingException, IOException { 45 | return inventoryService.getInventory(invId); 46 | } 47 | 48 | @PutMapping(value="/{invId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 49 | @ResponseStatus(HttpStatus.OK) 50 | public Inventory updateProduct(@PathVariable("invId") long invId, @RequestBody Inventory inventory) { 51 | return inventoryService.updateInventory(invId, inventory); 52 | } 53 | 54 | @ResponseStatus(HttpStatus.NO_CONTENT) 55 | @DeleteMapping(value="/{invId}") 56 | public void deleteProduct(@PathVariable("invId") long invId) { 57 | inventoryService.deleteInventory(invId); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/dtos/InventoryDTO.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.dtos; 2 | 3 | public class InventoryDTO { 4 | 5 | private Long invId; 6 | 7 | private Integer qty; 8 | 9 | private Long itemId; 10 | 11 | private String name; 12 | 13 | public InventoryDTO() {} 14 | 15 | public Long getInvId() { 16 | return invId; 17 | } 18 | 19 | public void setInvId(Long invId) { 20 | this.invId = invId; 21 | } 22 | 23 | public Integer getQty() { 24 | return qty; 25 | } 26 | 27 | public void setQty(Integer qty) { 28 | this.qty = qty; 29 | } 30 | 31 | public long getItemId() { 32 | return itemId; 33 | } 34 | 35 | public void setItemId(Long itemId) { 36 | this.itemId = itemId; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public void setName(String name) { 44 | this.name = name; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/entities/Inventory.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.entities; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | 8 | @Entity 9 | public class Inventory { 10 | 11 | @Id 12 | @GeneratedValue(strategy=GenerationType.IDENTITY) 13 | private Long invId; 14 | 15 | private Integer qty; 16 | 17 | private Long itemId; 18 | 19 | public Inventory() {} 20 | 21 | public Long getInvId() { 22 | return invId; 23 | } 24 | 25 | public void setInvId(Long invId) { 26 | this.invId = invId; 27 | } 28 | 29 | public Integer getQty() { 30 | return qty; 31 | } 32 | 33 | public void setQty(Integer qty) { 34 | this.qty = qty; 35 | } 36 | 37 | public long getItemId() { 38 | return itemId; 39 | } 40 | 41 | public void setItemId(Long itemId) { 42 | this.itemId = itemId; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/interfaces/ProductInventory.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.interfaces; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.redhat.productinventory.dtos.InventoryDTO; 8 | import com.redhat.productinventory.entities.Inventory; 9 | 10 | public interface ProductInventory { 11 | 12 | /** 13 | * @param invId 14 | */ 15 | void deleteInventory(long invId); 16 | 17 | /** 18 | * @param invId 19 | * @return 20 | * @throws IOException 21 | * @throws JsonProcessingException 22 | */ 23 | InventoryDTO getInventory(long invId) throws JsonProcessingException, IOException; 24 | 25 | /** 26 | * @param inventory 27 | * @return 28 | */ 29 | Inventory addInventory(Inventory inventory); 30 | 31 | /** 32 | * @param invId 33 | * @param inventory 34 | * @return 35 | */ 36 | Inventory updateInventory(long invId, Inventory inventory); 37 | 38 | /** 39 | * @param principal 40 | * @return 41 | * @throws JsonProcessingException 42 | * @throws IOException 43 | */ 44 | List showInventoryList(String token) throws JsonProcessingException, IOException; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/repositories/InventoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.redhat.productinventory.entities.Inventory; 6 | 7 | public interface InventoryRepository extends JpaRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/java/com/redhat/productinventory/services/ProductInventoryService.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory.services; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.http.HttpEntity; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.http.HttpMethod; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.web.client.RestClientException; 18 | import org.springframework.web.client.RestTemplate; 19 | 20 | import com.fasterxml.jackson.core.JsonProcessingException; 21 | import com.fasterxml.jackson.databind.JsonNode; 22 | import com.fasterxml.jackson.databind.ObjectMapper; 23 | import com.redhat.productinventory.dtos.InventoryDTO; 24 | import com.redhat.productinventory.entities.Inventory; 25 | import com.redhat.productinventory.interfaces.ProductInventory; 26 | import com.redhat.productinventory.repositories.InventoryRepository; 27 | 28 | @Service 29 | public class ProductInventoryService implements ProductInventory { 30 | 31 | @Autowired 32 | InventoryRepository inventoryRepository; 33 | 34 | @Value("${service.catalog.name}") 35 | private String productServiceName; 36 | 37 | @Autowired 38 | RestTemplate restTemplate; 39 | 40 | @Override 41 | public List showInventoryList(String token) throws JsonProcessingException, IOException { 42 | HttpHeaders headers = new HttpHeaders(); 43 | headers.add("Authorization", token); 44 | headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); 45 | HttpEntity requestheaders = new HttpEntity<>(headers); 46 | List invList = inventoryRepository.findAll(); 47 | List inventory = new ArrayList<>(); 48 | for(Inventory inv : invList) { 49 | InventoryDTO invDTO = new InventoryDTO(); 50 | invDTO.setInvId(inv.getInvId()); 51 | invDTO.setItemId(inv.getItemId()); 52 | invDTO.setQty(inv.getQty()); 53 | ResponseEntity response = restTemplate.exchange(getURI() + "/" + inv.getItemId(), HttpMethod.GET, requestheaders, String.class); 54 | JsonNode rootNode = new ObjectMapper().readTree(response.getBody()); 55 | invDTO.setName(rootNode.get("name").textValue()); 56 | inventory.add(invDTO); 57 | } 58 | return inventory; 59 | } 60 | 61 | @Override 62 | public void deleteInventory(long invId) { 63 | // TODO Auto-generated method stub 64 | 65 | } 66 | 67 | @Override 68 | public InventoryDTO getInventory(long invId) throws JsonProcessingException, IOException { 69 | Inventory inv = inventoryRepository.findOne(invId); 70 | InventoryDTO invDTO = new InventoryDTO(); 71 | invDTO.setInvId(inv.getInvId()); 72 | invDTO.setItemId(inv.getItemId()); 73 | invDTO.setQty(inv.getQty()); 74 | ResponseEntity response = restTemplate.getForEntity(getURI() + "/" + inv.getItemId(), String.class); 75 | JsonNode rootNode = new ObjectMapper().readTree(response.getBody()); 76 | invDTO.setName(rootNode.get("name").textValue()); 77 | return invDTO; 78 | } 79 | 80 | @Override 81 | public Inventory addInventory(Inventory inventory) { 82 | // TODO Auto-generated method stub 83 | return null; 84 | } 85 | 86 | @Override 87 | public Inventory updateInventory(long invId, Inventory inventory) { 88 | // TODO Auto-generated method stub 89 | return null; 90 | } 91 | 92 | private URI getURI() { 93 | StringBuilder builder = new StringBuilder("http://"); 94 | builder.append(productServiceName); 95 | return URI.create(builder.toString()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /spring/product-inventory/src/main/resources/application-openshift.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://postgres-ha.pgo.svc.cluster.local:5432/userdb?sslmode=disable 2 | #spring.datasource.username=postgres 3 | #spring.datasource.password=admin 4 | spring.datasource.driver-class-name=org.postgresql.Driver 5 | service.catalog.name=product-catalog.product.svc.cluster.local:8080/product 6 | server.port = 8090 7 | 8 | keycloak.realm=${SSO_REALM} 9 | keycloak.realm-key=${SSO_REALM_KEY} 10 | keycloak.auth-server-url=${SSO_AUTH_SERVER_URL} 11 | keycloak.credentials.secret=${SSO_CREDENTIALS_SECRET} 12 | keycloak.resource=${SSO_RESOURCE} 13 | keycloak.use-resource-role-mappings=true 14 | keycloak.bearer-only=true 15 | # Keycloak Enable CORS 16 | keycloak.cors = true 17 | 18 | keycloak.securityConstraints[0].securityCollections[0].name=application 19 | keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=user 20 | keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/inventory -------------------------------------------------------------------------------- /spring/product-inventory/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:mem:inventory;DB_CLOSE_ON_EXIT=FALSE 2 | spring.datasource.username=sa 3 | spring.datasource.password= 4 | spring.datasource.driver-class-name=org.h2.Driver 5 | server.port = 8090 6 | service.product.name=localhost:8080/product 7 | spring.application.name=product-inventory 8 | 9 | service.catalog.name=localhost:8080/product 10 | 11 | 12 | 13 | keycloak.realm=demorealm 14 | keycloak.realm-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkpuD0OFSa7+FGejO59SAO0FCpVnityn9w9N0KOuShUfgJekh28UAa62qgfSKp+WG6+NDR3bQSlfPzgXGm34Cuwdtht+uQ/MZPqZq2vyYbXg8iLgctEgCB/FOSjHDsZQFPrgcqPLrTDIY7Y0fYVQKa6g1kPP1n1d3QUDzgoJFicGqf/uEdo4pabIuXpoxKZG/AfMcIeaHzrvr3fQG2lHpe/bDhPRpr17uZXikgUzu8zLBGB7Zxn40HZc5m8YY2vmEqjRryrkdmOLXzUO6Jui7Sw1jriwW+AowuzdXq5L/KZlbWRzB//oxJkrQQtBmzXaF5S+FpJLggxR/ETM2p5dUoQIDAQAB 15 | keycloak.auth-server-url=http://localhost:8081/auth 16 | keycloak.credentials.secret=4bc83153-f5c7-4d8a-a3df-0f1a2e1aa562 17 | keycloak.resource=inventoryservice 18 | keycloak.use-resource-role-mappings=true 19 | keycloak.bearer-only=true 20 | # Keycloak Enable CORS 21 | keycloak.cors = true 22 | 23 | keycloak.securityConstraints[0].securityCollections[0].name=application 24 | keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=user 25 | keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/inventory -------------------------------------------------------------------------------- /spring/product-inventory/src/main/resources/bootstrap-openshift.yml: -------------------------------------------------------------------------------- 1 | spring.application.name: product-inventory 2 | spring.cloud.vault: 3 | host: vault.vault.svc 4 | port: 8200 5 | scheme: https 6 | authentication: KUBERNETES 7 | kubernetes: 8 | role: product-inventory 9 | service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token 10 | config: 11 | order: -10 12 | generic: 13 | enabled: false 14 | postgresql: 15 | enabled: true 16 | role: pg-readwrite 17 | backend: database 18 | username-property: spring.datasource.username 19 | password-property: spring.datasource.password 20 | -------------------------------------------------------------------------------- /spring/product-inventory/src/test/java/com/redhat/productinventory/ProductInventoryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.redhat.productinventory; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ProductInventoryApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /sso/README.md: -------------------------------------------------------------------------------- 1 | Install Red Hat SSO 7.1 with PostgreSQL Persistent Volume 2 | =============== 3 | 4 | Setup 5 | ----- 6 | 7 | ``` 8 | oc new-project rh-sso 9 | oc policy add-role-to-user view system:serviceaccount:$(oc project -q):sso-service-account 10 | oc create -f sso/service.sso.yaml 11 | 12 | ``` 13 | # Configuration via CLI 14 | 15 | Install the `kcadm.sh` CLI 16 | ``` 17 | curl https://downloads.jboss.org/keycloak/3.4.3.Final/keycloak-3.4.3.Final.tar.gz -o /tmp/keycloak-3.4.3.Final.tar.gz 18 | tar -zxvf /tmp/keycloak-3.4.3.Final.tar.gz -C /tmp 19 | export KEYCLOAK_HOME=/tmp/keycloak-3.4.3.Final 20 | export PATH=$PATH:$KEYCLOAK_HOME/bin 21 | ``` 22 | 23 | configure the OAuth server 24 | 25 | ``` 26 | export fqdn=`oc get route | grep secure-sso | awk '{print $2}'` 27 | export subdomain=${fqdn:`expr index "$fqdn" '.'`} 28 | export pod=`oc get pods | grep -m1 sso | awk '{print $1}'` 29 | oc rsync $pod:/var/run/secrets/java.io/keystores/truststore.jks /tmp/ 30 | kcadm.sh config truststore --storepass changeit /tmp/truststore.jks 31 | kcadm.sh config credentials --trustpass=changeit --server https://$fqdn/auth --realm master --user admin --password 2ukFR5Kh 32 | cat ./sso/inventoryservice-client.json | envsubst | kcadm.sh create clients --trustpass=changeit -r master -f - 33 | kcadm.sh create --trustpass=changeit clients/b5e0526f-25b5-452c-9d66-e56515402e7f/roles -r master -s name=user 34 | kcadm.sh create users --trustpass=changeit -r master -s username=demouser -s enabled=true 35 | kcadm.sh set-password --trustpass=changeit -r master --username demouser --new-password password 36 | kcadm.sh add-roles --trustpass changeit --uusername demouser --rolename user -r master --cclientid inventoryservice 37 | ``` 38 | 39 | Login & configuration 40 | ---------------- 41 | Navigate to the admin console using the newly created route and enter credential provided below: 42 | 43 | ``` 44 | 45 | https://secure-sso-rh-sso.LOCAL_OPENSHIFT_HOSTNAME 46 | 47 | 48 | ``` 49 | username: admin 50 | password: 2ukFR5Kh 51 | 52 | After logging in as admin please follow the below steps configuring RH-SSO to be used by applications: 53 | 54 | Click Clients in the sidebar under master realm configuration then click Create; in the Client Id dialog enter inventoryservice, then click Save. 55 | 56 | Once redirected to the settings tab in the Access Type drop-down menu, select confidential. 57 | 58 | Toggle the Direct Access Grants Enabled to On so the username/password can be exchanged for a token 59 | 60 | In the Valid Redirect URIs dialog, enter the URI for product-frontend application route. 61 | 62 | ``` 63 | 64 | http://product-frontend-product.LOCAL_OPENSHIFT_HOSTNAME/* 65 | 66 | 67 | ``` 68 | In the Web Origins URIs dialog add * and click Save, this will allow any originating URI to authenticate the bearer token. 69 | 70 | Then go to the Roles tab and click Add Role, enter the Role Name as user and click Save. 71 | 72 | Click Users in the sidebar under master realm Manage then click Add User; in the Username dialog enter demouser, then click Save. 73 | 74 | Once redirected to the Details tab navigate to the Credentials tab. 75 | 76 | Enter a password 'password' and toggle Temporary to OFF, then click Reset Password. Confirm the change. 77 | 78 | Navigate to the Role Mappings tab, under the Client Roles drop down select inventoryservice. 79 | 80 | In the Available Roles box select user and click Add Selected button under the box to assign the role. 81 | 82 | Test that you can ge the from the terminal: (You can copy the client_secret from the Credentials tab in the inventoryservice Client) 83 | 84 | ``` 85 | 86 | curl -sk -X POST https://secure-sso-rh-sso.LOCAL_OPENSHIFT_HOSTNAME/auth/realms/master/protocol/openid-connect/token -d grant_type=password -d username=demouser -d password=password -d client_id=inventoryservice -d client_secret=254deb12-6094-4860-ad1f-1b4aa3db62a0 | jq '.access_token' 87 | 88 | 89 | ``` 90 | 91 | References 92 | ------ 93 | 94 | * [Guide to using Red Hat JBoss SSO for OpenShift](https://access.redhat.com/documentation/en-us/red_hat_jboss_middleware_for_openshift/3/single/red_hat_jboss_sso_for_openshift/index) 95 | * [Snowdrop SSO YAML](https://github.com/snowdrop/spring-boot-http-secured-booster/blob/master/service.sso.yaml) 96 | 97 | 98 | Run Local Keycloak Docker Container 99 | ------ 100 | 101 | If you choose to run SSO/Keycloak locally outside of OpenShift, it is possible. Use version 2.5.1 of Keycloak which aligns with the version of Keycloak which RH SSO is built from: 102 | 103 | 104 | `docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8081:8080 jboss/keycloak:2.5.1.Final` 105 | -------------------------------------------------------------------------------- /sso/inventoryservice-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b5e0526f-25b5-452c-9d66-e56515402e7f", 3 | "clientId": "inventoryservice", 4 | "surrogateAuthRequired": false, 5 | "enabled": true, 6 | "clientAuthenticatorType": "client-secret", 7 | "redirectUris": [ 8 | "https://product-frontend-product.${subdomain}/*" 9 | ], 10 | "webOrigins": [ 11 | "*" 12 | ], 13 | "notBefore": 0, 14 | "bearerOnly": false, 15 | "consentRequired": false, 16 | "standardFlowEnabled": true, 17 | "implicitFlowEnabled": false, 18 | "directAccessGrantsEnabled": true, 19 | "serviceAccountsEnabled": false, 20 | "publicClient": false, 21 | "frontchannelLogout": false, 22 | "protocol": "openid-connect", 23 | "attributes": { 24 | "saml.assertion.signature": "false", 25 | "saml.force.post.binding": "false", 26 | "saml.multivalued.roles": "false", 27 | "saml.encrypt": "false", 28 | "saml_force_name_id_format": "false", 29 | "saml.client.signature": "false", 30 | "saml.authnstatement": "false", 31 | "saml.server.signature": "false", 32 | "saml.server.signature.keyinfo.ext": "false" 33 | }, 34 | "fullScopeAllowed": true, 35 | "nodeReRegistrationTimeout": -1, 36 | "protocolMappers": [ 37 | { 38 | "id": "b341abb1-059e-4400-953e-7617dfd43bff", 39 | "name": "role list", 40 | "protocol": "saml", 41 | "protocolMapper": "saml-role-list-mapper", 42 | "consentRequired": false, 43 | "config": { 44 | "single": "false", 45 | "attribute.nameformat": "Basic", 46 | "attribute.name": "Role" 47 | } 48 | }, 49 | { 50 | "id": "c90205a8-1164-48b0-9b19-4411920a1ab7", 51 | "name": "username", 52 | "protocol": "openid-connect", 53 | "protocolMapper": "oidc-usermodel-property-mapper", 54 | "consentRequired": true, 55 | "consentText": "${username}", 56 | "config": { 57 | "userinfo.token.claim": "true", 58 | "user.attribute": "username", 59 | "id.token.claim": "true", 60 | "access.token.claim": "true", 61 | "claim.name": "preferred_username", 62 | "jsonType.label": "String" 63 | } 64 | }, 65 | { 66 | "id": "70792a41-b536-40af-9597-c87c0da20d31", 67 | "name": "email", 68 | "protocol": "openid-connect", 69 | "protocolMapper": "oidc-usermodel-property-mapper", 70 | "consentRequired": true, 71 | "consentText": "${email}", 72 | "config": { 73 | "userinfo.token.claim": "true", 74 | "user.attribute": "email", 75 | "id.token.claim": "true", 76 | "access.token.claim": "true", 77 | "claim.name": "email", 78 | "jsonType.label": "String" 79 | } 80 | }, 81 | { 82 | "id": "c4b8bc4b-92f4-48df-bb2c-0f6909f4c059", 83 | "name": "family name", 84 | "protocol": "openid-connect", 85 | "protocolMapper": "oidc-usermodel-property-mapper", 86 | "consentRequired": true, 87 | "consentText": "${familyName}", 88 | "config": { 89 | "userinfo.token.claim": "true", 90 | "user.attribute": "lastName", 91 | "id.token.claim": "true", 92 | "access.token.claim": "true", 93 | "claim.name": "family_name", 94 | "jsonType.label": "String" 95 | } 96 | }, 97 | { 98 | "id": "c5716e2a-1bb3-4298-8ad5-0ae028df5146", 99 | "name": "given name", 100 | "protocol": "openid-connect", 101 | "protocolMapper": "oidc-usermodel-property-mapper", 102 | "consentRequired": true, 103 | "consentText": "${givenName}", 104 | "config": { 105 | "userinfo.token.claim": "true", 106 | "user.attribute": "firstName", 107 | "id.token.claim": "true", 108 | "access.token.claim": "true", 109 | "claim.name": "given_name", 110 | "jsonType.label": "String" 111 | } 112 | }, 113 | { 114 | "id": "e6ee76a2-3b74-4c89-af5a-ecf30801f70b", 115 | "name": "full name", 116 | "protocol": "openid-connect", 117 | "protocolMapper": "oidc-full-name-mapper", 118 | "consentRequired": true, 119 | "consentText": "${fullName}", 120 | "config": { 121 | "id.token.claim": "true", 122 | "access.token.claim": "true" 123 | } 124 | } 125 | ], 126 | "useTemplateConfig": false, 127 | "useTemplateScope": false, 128 | "useTemplateMappers": false 129 | } -------------------------------------------------------------------------------- /vault/.gitignore: -------------------------------------------------------------------------------- 1 | /vault_keys.txt 2 | -------------------------------------------------------------------------------- /vault/app-policy.hcl: -------------------------------------------------------------------------------- 1 | # Allow a token to get a secret from the generic secret backend 2 | # for the client role. 3 | path "database/creds/pg-readwrite" { 4 | capabilities = ["read"] 5 | } 6 | -------------------------------------------------------------------------------- /vault/deploy-vault.md: -------------------------------------------------------------------------------- 1 | # Install Vault CLI 2 | 3 | You first may need to `sudo yum install unzip` if you don't have unzip installed. 4 | 5 | ``` 6 | curl -o /tmp/vault_0.9.1_linux_amd64.zip https://releases.hashicorp.com/vault/0.9.1/vault_0.9.1_linux_amd64.zip?_ga=2.50315055.849435059.1516247977-364835320.1511883273 7 | unzip /tmp/vault_0.9.1_linux_amd64.zip -d /tmp 8 | sudo cp /tmp/vault /usr/bin/ 9 | ``` 10 | 11 | # Install Vault 12 | ``` 13 | oc new-project vault 14 | oc adm policy add-scc-to-user anyuid -z default 15 | oc create configmap vault-config --from-file=vault-config=./vault/vault-config.json 16 | oc create -f ./vault/vault.yaml 17 | oc create route reencrypt vault --port=8200 --service=vault 18 | ``` 19 | # Initialize Vault 20 | ``` 21 | export VAULT_ADDR=https://`oc get route | grep -m1 vault | awk '{print $2}'` 22 | vault init -tls-skip-verify -key-shares=1 -key-threshold=1 23 | ``` 24 | Save the generated key and token. 25 | 26 | # Unseal Vault. 27 | 28 | You have to repeat this step every time you start vault. 29 | 30 | Don't try to automate this step, this is manual by design. 31 | 32 | You can make the initial seal stronger by increasing the number of keys. 33 | 34 | We will assume that the KEYS environment variable contains the key necessary to unseal the vault and that ROOT_TOKEN contains the root token. 35 | 36 | For example: 37 | 38 | `export KEYS=tjgv5s7M4CtMeUz92dU9jV3EudPawgNz6euEnciZoFs=` 39 | 40 | 41 | `export ROOT_TOKEN=1487cceb-f05d-63be-3e24-d08e429c760c` 42 | 43 | 44 | 45 | ``` 46 | vault unseal -tls-skip-verify $KEYS 47 | ``` 48 | -------------------------------------------------------------------------------- /vault/vault-config.json: -------------------------------------------------------------------------------- 1 | {"backend": 2 | {"file": 3 | {"path": "/vault/file"} 4 | }, 5 | "default_lease_ttl": "168h", 6 | "max_lease_ttl": "720h" , 7 | "disable_mlock": true, 8 | "listener": 9 | { "tcp" : 10 | { "address" : "0.0.0.0:8200" , 11 | "tls_cert_file" : "/var/run/secrets/kubernetes.io/certs/tls.crt", 12 | "tls_key_file" : "/var/run/secrets/kubernetes.io/certs/tls.key"} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vault/vault-kube-backend.md: -------------------------------------------------------------------------------- 1 | # Configure the Kubernetes Authentication Backend for Vault 2 | 3 | In this tutorial we are going to configure Vault to use the native [kubernetes autentication method](https://www.vaultproject.io/docs/auth/kubernetes.html). 4 | 5 | The below picture shows the workflow. 6 | 7 | TODO 8 | 9 | Assuming that vault is already installed in the Vault, you should have the `ROOT_TOKEN` variable initialized. 10 | Follow the below steps to enable the Kubernetes back end authentication: 11 | ``` 12 | oc project vault 13 | oc create sa vault-auth 14 | oc adm policy add-cluster-role-to-user system:auth-delegator -z vault-auth 15 | secret=`oc describe sa vault-auth | grep 'Tokens:' | awk '{print $2}'` 16 | token=`oc describe secret $secret | grep 'token:' | awk '{print $2}'` 17 | pod=`oc get pods | grep vault | awk '{print $1}'` 18 | oc exec $pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt >> ca.crt 19 | export VAULT_TOKEN=$ROOT_TOKEN 20 | vault auth-enable -tls-skip-verify kubernetes 21 | vault write -tls-skip-verify auth/kubernetes/config token_reviewer_jwt=$token kubernetes_host=https://kubernetes.default.svc:443 kubernetes_ca_cert=@ca.crt 22 | rm ca.crt 23 | ``` -------------------------------------------------------------------------------- /vault/vault-postgres.md: -------------------------------------------------------------------------------- 1 | # configure the prostresql database secret backend 2 | 3 | ``` 4 | vault mount -tls-skip-verify database 5 | 6 | vault write -tls-skip-verify database/config/postgresql \ 7 | plugin_name=postgresql-database-plugin \ 8 | allowed_roles="pg-readwrite" \ 9 | connection_url="postgresql://postgres:admin@postgres-ha.pgo.svc.cluster.local:5432/userdb?sslmode=disable" 10 | 11 | vault write -tls-skip-verify database/roles/pg-readwrite \ 12 | db_name=postgresql \ 13 | creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \ 14 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ 15 | default_ttl="1h" \ 16 | max_ttl="24h" 17 | 18 | ``` 19 | 20 | Create a policy that allows to read from role pg-readwrite 21 | ``` 22 | export VAULT_TOKEN=$ROOT_TOKEN 23 | vault policy-write -tls-skip-verify pg-readwrite ./vault/app-policy.hcl 24 | ``` 25 | Bind the policy to the `product-catalog` and the `product-inventory` role. 26 | ``` 27 | vault write -tls-skip-verify auth/kubernetes/role/product-catalog bound_service_account_names=default bound_service_account_namespaces='*' policies=pg-readwrite ttl=1h 28 | vault write -tls-skip-verify auth/kubernetes/role/product-inventory bound_service_account_names=default bound_service_account_namespaces='*' policies=pg-readwrite ttl=1h 29 | ``` 30 | 31 | # Test the configuration 32 | 33 | ``` 34 | secret=`oc describe sa default | grep 'Tokens:' | awk '{print $2}'` 35 | token=`oc describe secret $secret | grep 'token:' | awk '{print $2}'` 36 | VAULT_TOKEN=`vault write -tls-skip-verify auth/kubernetes/login role=product-catalog jwt=$token | grep -w token | awk 'NR==1{print $2}'` 37 | vault read -tls-skip-verify database/creds/pg-readwrite 38 | ``` 39 | you should see an output similar to this: 40 | ``` 41 | Key Value 42 | --- ----- 43 | lease_id database/creds/pg-readwrite/93f0d34f-081c-dd99-e93a-f19c33ae75af 44 | lease_duration 1h0m0s 45 | lease_renewable true 46 | password A1a-96vrxq3uu7ru15yt 47 | username v-root-pg-readw-85z1u6urx2wx9t7pxv61-1516981399 48 | ``` -------------------------------------------------------------------------------- /vault/vault.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: vault 5 | annotations: 6 | service.alpha.openshift.io/serving-cert-secret-name: vault-cert 7 | labels: 8 | app: vault 9 | spec: 10 | ports: 11 | - name: vault 12 | port: 8200 13 | selector: 14 | app: vault 15 | --- 16 | apiVersion: v1 17 | kind: DeploymentConfig 18 | metadata: 19 | labels: 20 | app: vault 21 | name: vault 22 | spec: 23 | replicas: 1 24 | template: 25 | metadata: 26 | labels: 27 | app: vault 28 | spec: 29 | containers: 30 | - image: vault:latest 31 | name: vault 32 | ports: 33 | - containerPort: 8200 34 | name: vaultport 35 | protocol: TCP 36 | args: 37 | - server 38 | - -log-level=debug 39 | env: 40 | - name: SKIP_SETCAP 41 | value: 'true' 42 | - name: VAULT_LOCAL_CONFIG 43 | valueFrom: 44 | configMapKeyRef: 45 | name: vault-config 46 | key: vault-config 47 | volumeMounts: 48 | - name: vault-file-backend 49 | mountPath: /vault/file 50 | readOnly: false 51 | - name: vault-cert 52 | mountPath: /var/run/secrets/kubernetes.io/certs 53 | livenessProbe: 54 | httpGet: 55 | path: 'v1/sys/health?standbyok=true&standbycode=200&sealedcode=200&uninitcode=200' 56 | port: 8200 57 | scheme: HTTPS 58 | readinessProbe: 59 | httpGet: 60 | path: 'v1/sys/health?standbyok=true&standbycode=200&sealedcode=200&uninitcode=200' 61 | port: 8200 62 | scheme: HTTPS 63 | volumes: 64 | - name: vault-file-backend 65 | persistentVolumeClaim: 66 | claimName: vault-file-backend 67 | - name: vault-cert 68 | secret: 69 | secretName: vault-cert 70 | --- 71 | kind: PersistentVolumeClaim 72 | apiVersion: v1 73 | metadata: 74 | name: vault-file-backend 75 | spec: 76 | accessModes: 77 | - ReadWriteOnce 78 | resources: 79 | requests: 80 | storage: 1Gi 81 | --------------------------------------------------------------------------------