[Solution] Update to ONAP Montreal releases version 18/12618/13
authordemskeq8 <alexander.dehn@highstreet-technologies.com>
Wed, 20 Mar 2024 07:50:14 +0000 (08:50 +0100)
committerdemskeq8 <alexander.dehn@highstreet-technologies.com>
Mon, 2 Sep 2024 07:34:24 +0000 (09:34 +0200)
- update controller/odlux images
- change persistence from elasticsearch to mariaDB
- improved user handling for identity
- support indentity theme
Issue-ID: OAM-403
Change-Id: I28bec223a656af1dcfe61b47f5df43fba4545a52
Signed-off-by: demskeq8 <alexander.dehn@highstreet-technologies.com>
25 files changed:
solution/README.md
solution/adopt_to_environment.py
solution/append_to_etc_hosts.sh [new file with mode: 0755]
solution/create_users.py [new file with mode: 0644]
solution/setup.sh [new file with mode: 0755]
solution/smo/common/.env
solution/smo/common/docker-compose.yaml
solution/smo/common/identity/authentication.json
solution/smo/common/identity/config.py
solution/smo/common/identity/themes/README.md [new file with mode: 0644]
solution/smo/common/identity/themes/oam/README.md [new file with mode: 0644]
solution/smo/common/identity/themes/oam/account/theme.properties [new file with mode: 0644]
solution/smo/common/identity/themes/oam/admin/theme.properties [new file with mode: 0644]
solution/smo/common/identity/themes/oam/email/theme.properties [new file with mode: 0644]
solution/smo/common/identity/themes/oam/login/resources/css/styles.css [new file with mode: 0644]
solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png [new file with mode: 0644]
solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png [new file with mode: 0644]
solution/smo/common/identity/themes/oam/login/theme.properties [new file with mode: 0644]
solution/smo/common/identity/themes/oam/welcome/theme.properties [new file with mode: 0644]
solution/smo/oam/.env
solution/smo/oam/controller/mountpoint-registrar.properties
solution/smo/oam/controller/oauth-aaa-app-config.xml
solution/smo/oam/docker-compose.yaml
solution/teardown.sh [new file with mode: 0755]
solution/users.csv [new file with mode: 0644]

index 036b803..7aa454f 100644 (file)
@@ -111,7 +111,7 @@ For development purposes <your-system> and <deployment-system> may reference the
 ```
 $ cat /etc/hosts
 127.0.0.1                     localhost
-127.0.1.1                     <your-system>
+127.0.1.1                     10.20.35.165
 
 # SMO OAM development system
 <deployment-system-ipv4>                   smo.o-ran-sc.org
@@ -136,8 +136,14 @@ $ cat /etc/hosts
 The following commands should be invoked. More detailed can be found in the
 next chapters.
 
-```
-docker compose -f smo/common/docker-compose.yaml up -d
+```bash
+docker compose -f smo/common/docker-compose.yaml up -d  --wait
+
+# optionally adjust the users.csv file to create new users
+vim users.csv
+# override authentication.json with the new users
+python3 create_users.py users.csv -o smo/common/identity/authentication.json
+
 python smo/common/identity/config.py
 
 docker compose -f smo/oam/docker-compose.yaml up -d
index 0993659..9b531cc 100755 (executable)
@@ -17,6 +17,7 @@
 
 import os
 import argparse
+from jinja2 import Template
 
 default_ip_address = 'aaa.bbb.ccc.ddd'
 default_http_domain = 'smo.o-ran-sc.org'
@@ -27,9 +28,13 @@ file_extensions = ['.env', '.yaml', '.json']
 
 parser = argparse.ArgumentParser(script_name)
 required = parser.add_argument_group('required named arguments')
-required.add_argument("-i", "--ip_address", help="The remote accessable IP address of this system.", type=str, required=True)
-parser.add_argument("-d", "--http_domain", help="The http domain. Default is " + default_http_domain + ".", type=str, default=default_http_domain)
-parser.add_argument("-r", "--revert", help="Reverts the previous made changes.", action='store_true')
+required.add_argument("-i", "--ip_address", help="The remote accessable IP address of this system.",
+                      type=str, required=True)
+parser.add_argument("-d", "--http_domain", help="The http domain. Default is " +
+                    default_http_domain + ".",
+                    type=str, default=default_http_domain)
+parser.add_argument("-r", "--revert", help="Reverts the previous made changes.",
+                    action='store_true')
 args = parser.parse_args()
 
 def find_replace(directory, find_text, replace_text, extensions):
@@ -45,6 +50,33 @@ def find_replace(directory, find_text, replace_text, extensions):
                         with open(file_path, 'w') as file:
                             file.write(updated_content)
                         print(f"Replaced '{find_text}' with '{replace_text}' in '{file_path}'")
+def create_etc_hosts(ip_adress_v4: str, http_domain: str ) -> None:
+    """
+    creates scelaton for /etc/hosts and writes to local file
+    @param ip_adress: ipv4 address of the system
+    @param http_domain: base domain name for the deployment
+    """
+    template_str = """
+# SMO OAM development system
+{{ deployment_system_ipv4 }}                   {{ http_domain }}
+{{ deployment_system_ipv4 }}           gateway.{{ http_domain }}
+{{ deployment_system_ipv4 }}          identity.{{ http_domain }}
+{{ deployment_system_ipv4 }}          messages.{{ http_domain }}
+{{ deployment_system_ipv4 }}      kafka-bridge.{{ http_domain }}
+{{ deployment_system_ipv4 }}         odlux.oam.{{ http_domain }}
+{{ deployment_system_ipv4 }}         flows.oam.{{ http_domain }}
+{{ deployment_system_ipv4 }}         tests.oam.{{ http_domain }}
+{{ deployment_system_ipv4 }}    controller.dcn.{{ http_domain }}
+{{ deployment_system_ipv4 }} ves-collector.dcn.{{ http_domain }}
+
+"""
+    template = Template(template_str)
+    hosts_entries: str = template.render(deployment_system_ipv4=ip_adress_v4,
+                                         http_domain=http_domain)
+    output_txt_path = f"{directory_path}/append_to_etc_hosts.txt"
+    with open(output_txt_path, 'w', encoding="utf-8") as f:
+        f.write(hosts_entries)
+    print(f"/etc/hosts entries created: {output_txt_path}")
 
 if args.revert == False:
     # replace ip
@@ -52,11 +84,14 @@ if args.revert == False:
 
     # replace domain
     if not args.http_domain == default_http_domain:
-      find_replace(directory_path, default_http_domain, args.http_domain, file_extensions)
+        find_replace(directory_path, default_http_domain, args.http_domain, file_extensions)
+    # write append file for etc/hosts
+    create_etc_hosts(ip_adress_v4=args.ip_address, http_domain=args.http_domain)
 else:
     # revert back ip
     find_replace(directory_path, args.ip_address, default_ip_address, file_extensions)
 
     # revert back domain
     if not args.http_domain == default_http_domain:
-      find_replace(directory_path, args.http_domain, default_http_domain, file_extensions)
+        find_replace(directory_path, args.http_domain, default_http_domain, file_extensions)
+
diff --git a/solution/append_to_etc_hosts.sh b/solution/append_to_etc_hosts.sh
new file mode 100755 (executable)
index 0000000..779dc48
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+cat $SCRIPT_DIR/append_to_etc_hosts.txt >> /etc/hosts
+cat /etc/hosts
\ No newline at end of file
diff --git a/solution/create_users.py b/solution/create_users.py
new file mode 100644 (file)
index 0000000..5b0964c
--- /dev/null
@@ -0,0 +1,88 @@
+import csv
+import json
+from argparse import ArgumentParser
+
+from jinja2 import Template
+
+
+class UserCreator:
+    template_str = """
+    {
+      "users": [
+        {% for user in users %}
+        {
+          "firstName": "{{ user.firstName }}",
+          "lastName": "{{ user.lastName }}",
+          "email": "{{ user.email }}",
+          "enabled": "{{ user.enabled }}",
+          "username": "{{ user.username }}",
+          "credentials": [
+            {
+              "type": "password",
+              "value": "{{ user.password }}",
+              {% if force_pwd_change is false %}
+              "temporary": "{{ user.force_pwd_change }}"
+              {% else %}
+              "temporary": "true"
+              {% endif %}
+            }
+          ],
+          "requiredActions": [
+            "UPDATE_PASSWORD"
+          ]
+        }{% if not loop.last %},{% endif %}
+        {% endfor %}
+      ],
+      "grants": [
+        {% for user in users %}
+        {
+          "username": "{{ user.username }}",
+          "role": "{{ user.role }}"
+        }{% if not loop.last %},{% endif %}
+        {% endfor %}
+      ]
+    }
+    """
+
+    def __init__(self, csv_file: str):
+        self.csv_file_path: str = csv_file
+
+    def get_users_from_csv(self) -> list[dict]:
+        """
+        Get the users from the CSV file
+        @return: list of users
+        """
+        users: list[dict] = []
+        with open(self.csv_file_path, "r") as file:
+            dict_reader: csv.DictReader = csv.DictReader(file)
+            for row in dict_reader:
+                users.append(row)
+        return users
+
+    def create_users_json(self, users: list[dict], output_json_path: str, force_pwd_change: bool ) -> None:
+        """
+        Create the users JSON from the users list. Uses Jinja2 template to create the JSON.
+        @param users: list of users to create the JSON
+        @param output_json_path: path to the output JSON file
+        @return: JSON string
+        """
+        if force_pwd_change is True:
+            print("Enforce password change for all users!")
+        template = Template(self.template_str)
+        users_json: str = template.render(users=users, force_pwd_change=force_pwd_change)
+        with open(output_json_path, 'w') as f:
+            json.dump(json.loads(users_json), f, indent=4)
+        print(f"Users JSON file created at {output_json_path}")
+
+
+if __name__ == '__main__':
+    parser = ArgumentParser(description="Create users JSON file from CSV file")
+    parser.add_argument("csv_file_path", type=str, help="Path to the CSV file containing the users data")
+    parser.add_argument("--output", "-o", type=str, required=False, default="authentication.json",
+                        help="Path to the output JSON file e.g. authentication.json")
+    parser.add_argument("--force-pwd-change","-f", action='store_true', help="Enforce password change for all users, overwrites value in user data" )
+
+    args = parser.parse_args()
+    user_creator = UserCreator(args.csv_file_path)
+    user_list = user_creator.get_users_from_csv()
+    user_creator.create_users_json(user_list, args.output, args.force_pwd_change)
diff --git a/solution/setup.sh b/solution/setup.sh
new file mode 100755 (executable)
index 0000000..e434fa0
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+docker compose -f $SCRIPT_DIR/smo/common/docker-compose.yaml up -d --wait
+python3 create_users.py $SCRIPT_DIR/users.csv -o $SCRIPT_DIR/smo/common/identity/authentication.json
+python3 $SCRIPT_DIR/smo/common/identity/config.py
+docker compose -f $SCRIPT_DIR/smo/oam/docker-compose.yaml up -d
+
+
+
index cc337b4..392b892 100644 (file)
@@ -38,7 +38,7 @@ IDENTITY_MGMT_PASSWORD=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U
 IDENTITY_PROVIDER_URL=https://identity.${HTTP_DOMAIN}
 
 # PERSISTENCE (including SDN-R Database)
-PERSISTENCE_IMAGE=docker.elastic.co/elasticsearch/elasticsearch-oss:7.9.3
+PERSISTENCE_IMAGE=mariadb:11.1.2
 
 ## ZooKeeper
 ZOOKEEPER_IMAGE=nexus3.onap.org:10001/onap/dmaap/zookeeper:6.0.3
index 7afa49b..772d073 100755 (executable)
 # limitations under the License.
 #
 # no more versions needed! Compose spec supports all features w/o a version
+version: "3.8"
 services:
-
   gateway:
     image: ${TRAEFIK_IMAGE}
     container_name: gateway
     hostname: gateway
     healthcheck:
       test:
-        - CMD
-        - traefik
-        - healthcheck
-        - --ping
+        [
+          "CMD",
+          "traefik",
+          "healthcheck",
+          "--ping"
+        ]
       interval: 10s
       timeout: 5s
       retries: 3
     restart: always
     ports:
-      - 80:80
-      - 443:443
-      - 4334:4334
-      - 4335:4335
+      - "80:80"
+      - "443:443"
+      - "4334:4334"
+      - "4335:4335"
     command:
       - --serverstransport.insecureskipverify=true
       - --log.level=${TRAEFIK_LOG_LEVEL}
@@ -72,6 +74,9 @@ services:
       traefik.http.middlewares.strip.stripprefix.prefixes: /traefik
       traefik.http.routers.gateway.tls: true
       traefik.http.services.gateway.loadbalancer.server.port: 8080
+      app: "gateway"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     networks:
       dmz:
       dcn:
@@ -81,44 +86,53 @@ services:
     container_name: identitydb
     hostname: identitydb
     environment:
-      - ALLOW_EMPTY_PASSWORD=no
-      - POSTGRESQL_USERNAME=keycloak
-      - POSTGRESQL_DATABASE=keycloak
-      - POSTGRESQL_PASSWORD=keycloak
+      ALLOW_EMPTY_PASSWORD: no
+      POSTGRESQL_USERNAME: keycloak
+      POSTGRESQL_DATABASE: keycloak
+      POSTGRESQL_PASSWORD: keycloak
+    labels:
+      app: "identitydb"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
 
   identity:
     image: ${IDENTITY_IMAGE}
     container_name: identity
     hostname: identity
     environment:
-      - KEYCLOAK_CREATE_ADMIN_USER=true
-      - KEYCLOAK_ADMIN_USER=${ADMIN_USERNAME}
-      - KEYCLOAK_ADMIN_PASSWORD=${ADMIN_PASSWORD}
-      - KEYCLOAK_MANAGEMENT_USER=${IDENTITY_MGMT_USERNAME}
-      - KEYCLOAK_MANAGEMENT_PASSWORD=${IDENTITY_MGMT_PASSWORD}
-      - KEYCLOAK_DATABASE_HOST=identitydb
-      - KEYCLOAK_DATABASE_NAME=keycloak
-      - KEYCLOAK_DATABASE_USER=keycloak
-      - KEYCLOAK_DATABASE_PASSWORD=keycloak
-      - KEYCLOAK_JDBC_PARAMS=sslmode=disable&connectTimeout=30000
-      - KEYCLOAK_PRODUCTION=false
-      - KEYCLOAK_ENABLE_TLS=true
-      - KEYCLOAK_TLS_KEYSTORE_FILE=/opt/bitnami/keycloak/certs/keystore.jks
-      - KEYCLOAK_TLS_TRUSTSTORE_FILE=/opt/bitnami/keycloak/certs/truststore.jks
-      - KEYCLOAK_TLS_KEYSTORE_PASSWORD=password
-      - KEYCLOAK_TLS_TRUSTSTORE_PASSWORD=changeit
+      KEYCLOAK_CREATE_ADMIN_USER: true
+      KEYCLOAK_ADMIN_USER: ${ADMIN_USERNAME}
+      KEYCLOAK_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
+      KEYCLOAK_MANAGEMENT_USER: ${IDENTITY_MGMT_USERNAME}
+      KEYCLOAK_MANAGEMENT_PASSWORD: ${IDENTITY_MGMT_PASSWORD}
+      KEYCLOAK_DATABASE_HOST: identitydb
+      KEYCLOAK_DATABASE_NAME: keycloak
+      KEYCLOAK_DATABASE_USER: keycloak
+      KEYCLOAK_DATABASE_PASSWORD: keycloak
+      KEYCLOAK_JDBC_PARAMS=sslmode: disable&connectTimeout=30000
+      KEYCLOAK_PRODUCTION: false
+      KEYCLOAK_ENABLE_TLS: true
+      KEYCLOAK_TLS_KEYSTORE_FILE: /opt/bitnami/keycloak/certs/keystore.jks
+      KEYCLOAK_TLS_TRUSTSTORE_FILE: /opt/bitnami/keycloak/certs/truststore.jks
+      KEYCLOAK_TLS_KEYSTORE_PASSWORD: password
+      KEYCLOAK_TLS_TRUSTSTORE_PASSWORD: changeit
+      KEYCLOAK_EXTRA_ARGS: "--spi-theme-default=oam"
     restart: unless-stopped
     volumes:
       - /etc/localtime:/etc/localtime:ro
       - ./identity/standalone.xml:/opt/jboss/keycloak/standalone/configuration/standalone.xml
       - ./identity/keystore.jks:/opt/bitnami/keycloak/certs/keystore.jks
       - ./identity/truststoreONAPall.jks:/opt/bitnami/keycloak/certs/truststore.jks
+      - ./identity/themes/oam:/opt/bitnami/keycloak/themes/oam
     labels:
       traefik.enable: true
       traefik.http.routers.identity.entrypoints: websecure
       traefik.http.routers.identity.rule: Host(`identity.${HTTP_DOMAIN}`)
       traefik.http.routers.identity.tls: true
       traefik.http.services.identity.loadbalancer.server.port: 8080
+      app: "identity"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     depends_on:
       identitydb:
         condition: service_started
@@ -132,7 +146,31 @@ services:
     image: ${PERSISTENCE_IMAGE}
     container_name: persistence
     environment:
-      - discovery.type=single-node
+      MARIADB_ROOT_PASSWORD: admin
+      MARIADB_DATABASE: sdnrdb
+      MARIADB_USER: sdnrdb
+      MARIADB_PASSWORD: sdnrdb
+      MARIADB_EXTRA_FLAGS: --bind-address=* --max_connections=400
+      MYSQL_ROOT_PASSWORD: admin
+      MYSQL_DATABASE: sdnrdb
+      MYSQL_USER: sdnrdb
+      MYSQL_PASSWORD: sdnrdb
+    labels:
+      app: "persistence"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
+    healthcheck:
+      interval: 30s
+      retries: 3
+      test:
+        [
+          "CMD",
+          "healthcheck.sh",
+          "--su-mysql",
+          "--connect",
+          "--innodb_initialized"
+        ]
+      timeout: 30s
 
   zookeeper:
     image: ${ZOOKEEPER_IMAGE}
@@ -150,6 +188,10 @@ services:
       ZOOKEEPER_SERVER_ID:
     volumes:
       - ./zookeeper/zk_server_jaas.conf:/etc/zookeeper/secrets/jaas/zk_server_jaas.conf
+    labels:
+      app: "zookeeper"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
 
   kafka:
     image: ${KAFKA_IMAGE}
@@ -169,6 +211,10 @@ services:
       KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
       # Reduced the number of partitions only to avoid the timeout error for the first subscribe call in slow environment
       KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS: 1
+    labels:
+      app: "kafka"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     volumes:
       - ./kafka/zk_client_jaas.conf:/etc/kafka/secrets/jaas/zk_client_jaas.conf
     depends_on:
@@ -192,6 +238,9 @@ services:
       traefik.http.routers.kafka-bridge.rule: Host(`kafka-bridge.${HTTP_DOMAIN}`)
       traefik.http.routers.kafka-bridge.tls: true
       traefik.http.services.kafka-bridge.loadbalancer.server.port: 8080
+      app: "kafka-bridge"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     volumes:
       - ./kafka-bridge:/opt/strimzi/config
     depends_on:
@@ -222,6 +271,9 @@ services:
       traefik.http.routers.topology.rule: Host(`topology.${HTTP_DOMAIN}`)
       traefik.http.routers.topology.tls: true
       traefik.http.services.topology.loadbalancer.server.port: 8181
+      app: "topology"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     networks:
       dmz:
       default:
@@ -242,6 +294,9 @@ services:
       traefik.http.routers.messages.rule: Host(`messages.${HTTP_DOMAIN}`)
       traefik.http.routers.messages.tls: true
       traefik.http.services.messages.loadbalancer.server.port: 3904
+      app: "messages"
+      deploy: "o-ran-sc-smo-common"
+      solution: "o-ran-sc-smo"
     depends_on:
       kafka:
         condition: service_started
index 2f91979..868c790 100644 (file)
 {
-  "users": [
-    {
-      "firstName": "Leia",
-      "lastName": "Organa",
-      "email": "leia.organa@sdnr.onap.org",
-      "enabled": "true",
-      "username": "leia.organa",
-      "credentials": [
+    "users": [
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "R2",
-      "lastName": "D2",
-      "email": "r2.d2@sdnr.onap.org",
-      "enabled": "true",
-      "username": "r2.d2",
-      "credentials": [
+            "firstName": "Leia",
+            "lastName": "Organa",
+            "email": "leia.organa@sdnr.onap.org",
+            "enabled": "",
+            "username": "leia.organa",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Luke",
-      "lastName": "Skywalker",
-      "email": "luke.skywalker@sdnr.onap.org",
-      "enabled": "true",
-      "username": "luke.skywalker",
-      "credentials": [
+            "firstName": "R2",
+            "lastName": "D2",
+            "email": "r2.d2@sdnr.onap.org",
+            "enabled": "",
+            "username": "r2.d2",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
-        }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Jargo",
-      "lastName": "Fett",
-      "email": "jargo.fett@sdnr.onap.org",
-      "enabled": "true",
-      "username": "jargo.fett",
-      "credentials": [
+            "firstName": "Luke",
+            "lastName": "Skywalker",
+            "email": "luke.skywalker@sdnr.onap.org",
+            "enabled": "",
+            "username": "luke.skywalker",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
+        {
+            "firstName": "Jargo",
+            "lastName": "Fett",
+            "email": "jargo.fett@sdnr.onap.org",
+            "enabled": "",
+            "username": "jargo.fett",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
+            "firstName": "Martin",
+            "lastName": "Skorupski",
+            "email": "martin.skorupski@highstreet-technologies.com",
+            "enabled": "",
+            "username": "martin.skorupski",
+            "credentials": [
+                {
+                    "type": "password",
+                    "value": "Default4SDN!",
+                    "temporary": "true"
+                }
+            ],
+            "requiredActions": [
+                "UPDATE_PASSWORD"
+            ]
         }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    },
-    {
-      "firstName": "Martin",
-      "lastName": "Skorupski",
-      "email": "martin.skorupski@highstreet-technologies.com",
-      "enabled": "true",
-      "username": "martin.skorupski",
-      "credentials": [
+    ],
+    "grants": [
+        {
+            "username": "leia.organa",
+            "role": "administration"
+        },
+        {
+            "username": "r2.d2",
+            "role": "administration"
+        },
+        {
+            "username": "luke.skywalker",
+            "role": "provision"
+        },
+        {
+            "username": "jargo.fett",
+            "role": "supervision"
+        },
         {
-          "type": "password",
-          "value": "Default4SDN!",
-          "temporary": true
+            "username": "martin.skorupski",
+            "role": "administration"
         }
-      ],
-      "requiredActions": [
-        "UPDATE_PASSWORD"
-      ]
-    }
-  ],
-  "grants": [
-    {
-      "username": "leia.organa",
-      "role": "administration"
-    },
-    {
-      "username": "r2.d2",
-      "role": "administration"
-    },
-    {
-      "username": "luke.skywalker",
-      "role": "provision"
-    },
-    {
-      "username": "jargo.fett",
-      "role": "supervision"
-    },
-    {
-      "username": "martin.skorupski",
-      "role": "administration"
-    }
-  ]
+    ]
 }
\ No newline at end of file
index 5a8bf44..37967e1 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
-#############################################################################
-# Copyright 2023 highstreet technologies GmbH
+################################################################################
+# Copyright 2021 highstreet technologies GmbH
 #
 # Licensed under the Apache License, Version 2.0 (the 'License');
 # you may not use this file except in compliance with the License.
 #
 
 # importing the sys, json, requests library
+from sqlite3 import TimeFromTicks
+from jproperties import Properties
 import os
-import pathlib
 import sys
 import json
 import time
-import getpass
-import requests
 import re
+import requests
+import getpass
 import warnings
-from jproperties import Properties
 from typing import List
+
 warnings.filterwarnings('ignore', message='Unverified HTTPS request')
-# global configurations
 
 
-def get_environment_variable(name):
+# global configurations
+def get_env(name):
     configs = Properties()
-    path = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
-    env_file = str(path.parent.absolute()) + '/.env'
-    with open(env_file, "rb") as read_prop:
+    envFile = os.path.dirname(os.path.abspath(__file__)) + '/' + '../' + '.env'
+
+    with open(envFile, "rb") as read_prop:
         configs.load(read_prop)
     value = configs.get(name).data
 
@@ -45,54 +46,61 @@ def get_environment_variable(name):
         match = next(matches, None)
         if match is None:
             break
-        inner = get_environment_variable(match.group(1))
-        value = value.replace("${" + match.group(1) + "}", inner )
+        inner = get_env(match.group(1))
+        value = value.replace("${" + match.group(1) + "}", inner)
     return value
 
 
-def load_arguments(args: List[str]) -> tuple:
-    realm_file = os.path.dirname(os.path.abspath(
-        __file__)) + '/o-ran-sc-realm.json'
-    auth_file = os.path.dirname(os.path.abspath(
-        __file__)) + '/authentication.json'
-    ready_timeout = 180
+def loadArgs(args: List[str]) -> tuple:
+    realmFile = os.path.dirname(os.path.abspath(__file__)) + '/o-ran-sc-realm.json'
+    authFile = os.path.dirname(os.path.abspath(__file__)) + '/authentication.json'
+    readyTimeout = 180
     args.pop(0)
     while len(args) > 0:
         arg = args.pop(0)
         if arg == '--auth' and len(args) > 0:
-            auth_file = args.pop(0)
-            print('overwriting auth file: {}'.format(auth_file))
+            authFile = args.pop(0)
+            print('overwriting auth file: {}'.format(authFile))
         elif arg == '--realm' and len(args) > 0:
-            realm_file = args.pop(0)
-            print('overwriting realm file: {}'.format(realm_file))
+            realmFile = args.pop(0)
+            print('overwriting realm file: {}'.format(realmFile))
         elif arg == '--timeout' and len(args) > 0:
-            ready_timeout = int(args.pop(0))
-            print('waiting for ready {} seconds'.format(ready_timeout))
+            readyTimeout = int(args.pop(0))
+            print('waiting for ready {} seconds'.format(readyTimeout))
 
-    return (realm_file, auth_file, ready_timeout)
+    return (realmFile, authFile, readyTimeout)
 
 
 def isReady(timeoutSeconds=180):
     url = getBaseUrl()
-    print(f'url={url}')
+    print(url)
+    response = None
+    print("waiting for ready state", end='')
     while timeoutSeconds > 0:
         try:
             response = requests.get(url, verify=False, headers={})
+            print(response)
         except:
-            response = None
+            pass
         if response is not None and response.status_code == 200:
+            print('succeeded')
             return True
         time.sleep(1)
         timeoutSeconds -= 1
+        print('.', end='', flush=True)
     return False
 
 
 def getBaseUrl():
-    return get_environment_variable("IDENTITY_PROVIDER_URL")
-
-# Request a token for further communication
+    try:
+        if get_env("USE_LOCAL_HOST_FOR_IDENTITY_CONFIG").strip("'\"") == "true":
+            return get_env("IDENTITY_PROVIDER_URL_LOCAL_HOST")
+    except AttributeError:
+        print("Using IDENTITY_PROVIDER_URL")
+    return get_env("IDENTITY_PROVIDER_URL")
 
 
+# Request a token for futher communication
 def getToken():
     url = base + '/realms/master/protocol/openid-connect/token'
     headers = {
@@ -106,8 +114,7 @@ def getToken():
         'password': password
     }
     try:
-        response = requests.post(url, verify=False, auth=(
-            username, password), data=body, headers=headers)
+        response = requests.post(url, verify=False, auth=(username, password), data=body, headers=headers)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -122,9 +129,8 @@ def getToken():
     else:
         sys.exit('Getting token failed.')
 
-# create the default realm from file
-
 
+# create the default realm from file
 def createRealm(token, realm):
     url = base + '/admin/realms'
     auth = 'bearer ' + token
@@ -134,8 +140,7 @@ def createRealm(token, realm):
         'authorization': auth
     }
     try:
-        response = requests.post(
-            url, verify=False, json=realm, headers=headers)
+        response = requests.post(url, verify=False, json=realm, headers=headers)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -146,9 +151,8 @@ def createRealm(token, realm):
 
     return response.status_code >= 200 and response.status_code < 300
 
-# Check if default realm exists
-
 
+# Check if default realm exists
 def checkRealmExists(token, realmId):
     url = base + '/admin/realms/' + realmId
     auth = 'bearer ' + token
@@ -172,9 +176,8 @@ def checkRealmExists(token, realmId):
         # sys.exit('Getting realm failed.')
         return False
 
-# create a user in default realm
-
 
+# create a user in default realm
 def createUser(token, realmConfig, user):
     realmId = realmConfig['id']
     url = base + '/admin/realms/' + realmId + '/users'
@@ -198,9 +201,8 @@ def createUser(token, realmConfig, user):
     else:
         print('User creation', user['username'], 'failed!\n', response.text)
 
-# creates User accounts in realm based a file
-
 
+# creates User accounts in realm based a file
 def createUsers(token, realmConfig, authConfig):
     for user in authConfig['users']:
         createUser(token, realmConfig, user)
@@ -216,24 +218,24 @@ def createUsers(token, realmConfig, authConfig):
             {
                 "type": "password",
                 "value": password,
-                "temporary": True
+                "temporary": False
             }
-        ],
-        "requiredActions": [
-            "UPDATE_PASSWORD"
         ]
     }
     createUser(token, realmConfig, systemUser)
 
-# Grants a role to a user
-
 
-def addUserRole(user: dict, role: dict, options: dict):
+# Grants a role to a user
+def addUserRole(user: dict, role: list, options: dict):
     url = options['url'] + '/' + user['id'] + '/role-mappings/realm'
     try:
-        response = requests.post(url, verify=False, json=[
-                                 {'id': role['id'], 'name':role['name']}],
-                                 headers=options['headers'])
+        for irole in role:
+            response = requests.post(url, verify=False, json=[{'id': irole['id'], 'name': irole['name']}],
+                                     headers=options['headers'])
+            if response.status_code >= 200 and response.status_code < 300:
+                print('User role', user['username'], irole['name'], 'created!')
+            else:
+                print('Creation of user role', user['username'], irole['name'], 'failed!\n', response.text)
     except requests.exceptions.Timeout:
         sys.exit('HTTP request failed, please check you internet connection.')
     except requests.exceptions.TooManyRedirects:
@@ -242,28 +244,24 @@ def addUserRole(user: dict, role: dict, options: dict):
         # catastrophic error. bail.
         raise SystemExit(e)
 
-    if response.status_code >= 200 and response.status_code < 300:
-        print('User role', user['username'], role['name'], 'created!')
-    else:
-        print('Creation of user role',
-              user['username'], role['name'], 'failed!\n', response.text)
 
 # searches for the role of a given user
-
-
 def findRole(username: str, authConfig: dict, realmConfig: dict) -> dict:
+    roleList = []
+    roleNames = []
     roleName = 'administration'
     for grant in authConfig['grants']:
         if grant['username'] == username:
             roleName = grant['role']
-    for role in realmConfig['roles']['realm']:
-        if role['name'] == roleName:
-            return role
-    return None
-
-# adds roles to users
+            roleNames = roleName.split(",")  # A user can have multiple roles, comma separated
+    for iroleName in roleNames:
+        for role in realmConfig['roles']['realm']:
+            if role['name'] == iroleName:
+                roleList.append(role)
+    return roleList
 
 
+# adds roles to users
 def addUserRoles(token, realmConfig, authConfig):
     realmId = realmConfig['id']
     url = base + '/admin/realms/' + realmId + '/users'
@@ -296,12 +294,12 @@ def addUserRoles(token, realmConfig, authConfig):
     else:
         sys.exit('Getting users failed.')
 
-# main
 
+# main
 
-(realmFile, authFile, readyTimeout) = load_arguments(sys.argv)
-username = get_environment_variable('ADMIN_USERNAME')
-password = get_environment_variable('ADMIN_PASSWORD')
+(realmFile, authFile, readyTimeout) = loadArgs(sys.argv)
+username = get_env('ADMIN_USERNAME')
+password = get_env('ADMIN_PASSWORD')
 base = getBaseUrl()
 isReady(readyTimeout)
 token = getToken()
diff --git a/solution/smo/common/identity/themes/README.md b/solution/smo/common/identity/themes/README.md
new file mode 100644 (file)
index 0000000..98d3823
--- /dev/null
@@ -0,0 +1,6 @@
+# add themes to solution
+- copy `org.keycloak.keycloak-themes-XX.Y.Z.jar` from image and unzip 
+- copy keycloak themes into directory a themes subdirectory directory with 
+- modify css and resources (see dev resoures in keycloak)
+- use `- KEYCLOAK_EXTRA_ARGS="--log-level=DEBUG --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false"` for online development
+- add  `KEYCLOAK_EXTRA_ARGS="--spi-theme-default=5gberlin"` to as environment in docker-compose.yml for identity to select as default theme
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/README.md b/solution/smo/common/identity/themes/oam/README.md
new file mode 100644 (file)
index 0000000..17e8a14
--- /dev/null
@@ -0,0 +1,16 @@
+# create custom theme
+
+A detailed description of the theme creation can be found in the [keycloak documentation](https://www.keycloak.org/docs/latest/server_development/#_themes).
+It's not necessary to create a new theme from scratch. You can inherit from a base theme and override only the necessary 
+parts.
+
+- use `- KEYCLOAK_EXTRA_ARGS="--log-level=DEBUG --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false"` for theme development
+
+# add themes to solution
+
+After creating the theme, you can add it to the solution. The following steps are necessary:
+
+- mount the themes directory into the keycloak container
+  - target directory: `/opt/bitnami/keycloak/themes/[custom-theme-name]`
+- add  `KEYCLOAK_EXTRA_ARGS="--spi-theme-default=[custom-theme-name]"` to the environment section in docker-compose.yml 
+  for identity to select as default theme
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/account/theme.properties b/solution/smo/common/identity/themes/oam/account/theme.properties
new file mode 100644 (file)
index 0000000..12d9c19
--- /dev/null
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/admin/theme.properties b/solution/smo/common/identity/themes/oam/admin/theme.properties
new file mode 100644 (file)
index 0000000..12d9c19
--- /dev/null
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/email/theme.properties b/solution/smo/common/identity/themes/oam/email/theme.properties
new file mode 100644 (file)
index 0000000..12d9c19
--- /dev/null
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/login/resources/css/styles.css b/solution/smo/common/identity/themes/oam/login/resources/css/styles.css
new file mode 100644 (file)
index 0000000..8cb33bc
--- /dev/null
@@ -0,0 +1,19 @@
+.login-pf body {
+    background: DimGrey none;
+}
+.login-pf body {
+    background: url("../img/o-ran-sc-smo-oam-keyclock-background.png") no-repeat center center fixed;
+    background-size: cover;
+    height: 100%;
+}
+div.kc-logo-text {
+    background-image: url(../img/o-ran-sc-logo.png);
+    background-repeat: no-repeat;
+    height: 63px;
+    width: 300px;
+    margin: 0 auto;
+}
+.card-pf {
+    background: #bbb;
+    opacity: 0.8;
+}
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png
new file mode 100644 (file)
index 0000000..c3b6ce5
Binary files /dev/null and b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-logo.png differ
diff --git a/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png
new file mode 100644 (file)
index 0000000..c8f24dd
Binary files /dev/null and b/solution/smo/common/identity/themes/oam/login/resources/img/o-ran-sc-smo-oam-keyclock-background.png differ
diff --git a/solution/smo/common/identity/themes/oam/login/theme.properties b/solution/smo/common/identity/themes/oam/login/theme.properties
new file mode 100644 (file)
index 0000000..cad2a08
--- /dev/null
@@ -0,0 +1,3 @@
+parent=keycloak
+import=common/keycloak
+styles=css/login.css css/styles.css
\ No newline at end of file
diff --git a/solution/smo/common/identity/themes/oam/welcome/theme.properties b/solution/smo/common/identity/themes/oam/welcome/theme.properties
new file mode 100644 (file)
index 0000000..12d9c19
--- /dev/null
@@ -0,0 +1,2 @@
+parent=keycloak
+import=common/keycloak
\ No newline at end of file
index 8541289..588a941 100644 (file)
@@ -28,13 +28,14 @@ HTTP_DOMAIN=smo.o-ran-sc.org
 IDENTITY_PROVIDER_URL=https://identity.${HTTP_DOMAIN}
 
 # SDN Controller
-SDNC_IMAGE=nexus3.onap.org:10001/onap/sdnc-image:2.4.2
+SDNC_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.6.1
 SDNC_REST_PORT=8181
+SDNR_WEBSOCKET_PORT=8182
 SDNC_CERT_DIR=/opt/opendaylight/current/certs
 SDNC_ENABLE_OAUTH=true
 
 # SDN Controller Web
-SDNC_WEB_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.4.2
+SDNC_WEB_IMAGE=nexus3.onap.org:10001/onap/sdnc-web-image:2.6.1
 SDNC_WEB_PORT=8080
 
 ## VES Collector
index 526c07a..7968841 100644 (file)
@@ -4,35 +4,43 @@ baseUrl=http://localhost:8181
 sdnrUser=admin
 sdnrPasswd=${ODL_ADMIN_PASSWORD}
 
+[strimzi-kafka]
+strimziEnabled=true
+bootstrapServers=kafka:9092
+securityProtocol=PLAINTEXT
+saslMechanism=PLAIN
+saslJaasConfig=PLAIN
+
+
 [fault]
-TransportType=HTTPNOAUTH
-host=messages:3904
 topic=unauthenticated.SEC_FAULT_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
-timeout=2000
-limit=1000
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
+fetchPause=5000
+
+[provisioning]
+topic=unauthenticated.SEC_3GPP_PROVISIONING_OUTPUT
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
 fetchPause=5000
 
 [pnfRegistration]
-TransportType=HTTPNOAUTH
-host=messages:3904
 topic=unauthenticated.VES_PNFREG_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
-timeout=2000
-limit=1000
+consumerGroup=myG
+consumerID=C1
+timeout=20000
+limit=10000
 fetchPause=5000
 
-[provisioning]
-TransportType=HTTPNOAUTH
-host=messages:3904
-topic=unauthenticated.SEC_3GPP_PROVISIONING_OUTPUT
-contenttype=application/json
-group=myG
-id=C1
+[stndDefinedFault]
+topic=unauthenticated.SEC_3GPP_FAULTSUPERVISION_OUTPUT
+consumerGroup=myG
+consumerID=C1
 timeout=20000
 limit=10000
-fetchPause=5000
\ No newline at end of file
+fetchPause=5000
+
index c210e37..8acb414 100644 (file)
   ~ Unless required by applicable law or agreed to in writing, software
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  ~ ============LICENSE_END=======================================================
-  ~
-  -->
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+~ ============LICENSE_END=======================================================
+    ~
+    -->
 
-<shiro-configuration xmlns="urn:opendaylight:aaa:app:config">
+        <shiro-configuration xmlns="urn:opendaylight:aaa:app:config">
 
 
     <main>
-        <pair-key>tokenAuthRealm</pair-key>
+    <pair-key>tokenAuthRealm</pair-key>
         <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.OAuth2Realm</pair-value>
-    </main>
+        </main>
 
     <main>
-        <pair-key>securityManager.realms</pair-key>
+    <pair-key>securityManager.realms</pair-key>
         <pair-value>$tokenAuthRealm</pair-value>
-    </main>
+        </main>
     <!-- Used to support OAuth2 use case. -->
-    <main>
-        <pair-key>authcBasic</pair-key>
-        <pair-value>org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter</pair-value>
-    </main>
     <main>
         <pair-key>anyroles</pair-key>
         <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.AnyRoleHttpAuthenticationFilter</pair-value>
-    </main>
+        </main>
     <main>
-        <pair-key>authcBearer</pair-key>
-        <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.BearerAndBasicHttpAuthenticationFilter</pair-value>
-    </main>
+    <pair-key>authcBearer</pair-key>
+    <!--        <pair-value>org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter</pair-value>-->
+    <pair-value>org.onap.ccsdk.features.sdnr.wt.oauthprovider.filters.BearerAndBasicHttpAuthenticationFilter</pair-value>
+        </main>
 
     <!-- in order to track AAA challenge attempts -->
     <main>
         <pair-value>org.opendaylight.aaa.shiro.filters.AuthenticationListener</pair-value>
     </main>
     <main>
-        <pair-key>securityManager.authenticator.authenticationListeners</pair-key>
-        <pair-value>$accountingListener</pair-value>
-    </main>
+    <pair-key>securityManager.authenticator.authenticationListeners</pair-key>
+    <pair-value>$accountingListener</pair-value>
+        </main>
 
     <!-- Model based authorization scheme supporting RBAC for REST endpoints -->
     <main>
-        <pair-key>dynamicAuthorization</pair-key>
-        <pair-value>org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter</pair-value>
-    </main>
+    <pair-key>dynamicAuthorization</pair-key>
+    <pair-value>org.opendaylight.aaa.shiro.realm.MDSALDynamicAuthorizationFilter</pair-value>
+        </main>
 
 
     <urls>
         <pair-key>/**/operations/cluster-admin**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
     <urls>
         <pair-key>/**/v1/**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
-    <!-- allow admin only access to write mdsal auth config -->
     <urls>
-        <pair-key>/rests/**/aaa*/**</pair-key>
-        <pair-value>authcBearer, roles[admin]</pair-value>
+    <pair-key>/rests/**/aaa*/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+
+    <urls>
+        <pair-key>/**/config/aaa*/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
-    <!-- anon access for login api -->
     <urls>
         <pair-key>/oauth/**</pair-key>
         <pair-value>anon</pair-value>
         <pair-key>/ready</pair-key>
         <pair-value>anon</pair-value>
     </urls>
-    <!-- anon access for odlux ui -->
     <urls>
-        <pair-key>/odlux/**</pair-key>
-        <pair-value>anon</pair-value>
-    </urls>
-    <!-- admin only access for apidocs -->
+    <pair-key>/apidoc/**</pair-key>
+        <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+    <!-- these two rules are needed for installCerts.py -->
     <urls>
-        <pair-key>/apidoc/**</pair-key>
+        <pair-key>/rests/data/network-topology:network-topology</pair-key>
         <pair-value>authcBasic, roles[admin]</pair-value>
     </urls>
     <urls>
-        <pair-key>/rests/**</pair-key>
-        <pair-value>authcBearer, anyroles["admin,provision"]</pair-value>
-    </urls>
-    <!-- any other access with configured dynamic filter -->
+    <pair-key>/rests/operations/netconf-keystore*</pair-key>
+    <pair-value>authcBasic, roles[admin]</pair-value>
+        </urls>
+
+    <!-- rfc8040 restconf access with configured dynamic filter -->
     <urls>
-        <pair-key>/**</pair-key>
-        <pair-value>authcBearer, anyroles["admin,provision"]</pair-value>
-    </urls>
+    <pair-key>/rests/**</pair-key>
+        <pair-value>authcBearer, dynamicAuthorization</pair-value>
+        </urls>
+    <!-- any other access with configured dynamic filter -->
+<urls>
+<pair-key>/**</pair-key>
+<pair-value>authcBearer, roles[admin]</pair-value>
+</urls>
 </shiro-configuration>
+
index 0042d22..bc827ae 100755 (executable)
@@ -15,8 +15,8 @@
 #
 
 # no more versions needed! Compose spec supports all features w/o a version
+version: "3.8"
 services:
-
   odlux:
     image: ${SDNC_WEB_IMAGE}
     container_name: odlux
@@ -28,12 +28,16 @@ services:
       SDNRPROTOCOL: http
       SDNRHOST: controller
       SDNRPORT: ${SDNC_REST_PORT}
+      SDNRWEBSOCKETPORT: ${SDNR_WEBSOCKET_PORT}
     labels:
       traefik.enable: true
       traefik.http.routers.sdnc-web.entrypoints: websecure
       traefik.http.routers.sdnc-web.rule: Host(`odlux.oam.${HTTP_DOMAIN}`)
       traefik.http.routers.sdnc-web.tls: true
       traefik.http.services.sdnc-web.loadbalancer.server.port: ${SDNC_WEB_PORT}
+      app: "odlux"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     depends_on:
       controller:
         condition: service_healthy
@@ -56,6 +60,7 @@ services:
     environment:
       ENABLE_ODL_CLUSTER: false
       ENABLE_OAUTH: ${SDNC_ENABLE_OAUTH}
+      ENABLE_ODLUX_RBAC: false
       ODL_CERT_DIR: ${SDNC_CERT_DIR}
       ODL_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
       SDNC_CONFIG_DIR: /opt/onap/ccsdk/data/properties
@@ -66,10 +71,14 @@ services:
       SDNRONLY: true
       SDNRINIT: true
       SDNRDM: true
-      SDNRDBURL: http://persistence:9200
+      SDNRDBTYPE: MARIADB
+      SDNRDBURL: jdbc:mysql://persistence:3306/sdnrdb
+      SDNRDBUSERNAME: sdnrdb
+      SDNRDBPASSWORD: sdnrdb
       SDNR_NETCONF_CALLHOME_ENABLED: true
       A1_ADAPTER_NORTHBOUND: false
       JAVA_OPTS: -Xms256m -Xmx4g
+      SDNR_WEBSOCKET_PORT: ${SDNR_WEBSOCKET_PORT}
       IDENTITY_PROVIDER_URL: ${IDENTITY_PROVIDER_URL}
       SDNC_WEB_URL: https://odlux.oam.${HTTP_DOMAIN}
       SDNR_VES_COLLECTOR_ENABLED: true
@@ -107,6 +116,9 @@ services:
       traefik.tcp.routers.controller-tls.tls: false
       traefik.tcp.routers.controller-tls.service: controller-tls
       traefik.tcp.services.controller-tls.loadbalancer.server.port: 4335
+      app: "controller"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     networks:
       smo:
       dcn:
@@ -117,6 +129,7 @@ services:
       context: ./ves-collector
       args:
         - BASEIMAGE=${VES_COLLECTOR_IMAGE}
+      network: host
     container_name: ves-collector
     hostname: ves-collector
     extra_hosts:
@@ -138,6 +151,9 @@ services:
       traefik.http.routers.ves.rule: Host(`ves-collector.dcn.${HTTP_DOMAIN}`)
       traefik.http.routers.ves.tls: true
       traefik.http.services.ves.loadbalancer.server.port: ${VES_ENDPOINT_PORT}
+      app: "ves-collector"
+      deploy: "o-ran-sc-smo-oam"
+      solution: "o-ran-sc-smo"
     networks:
       smo:
       dcn:
diff --git a/solution/teardown.sh b/solution/teardown.sh
new file mode 100755 (executable)
index 0000000..0e5340a
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+docker compose -f $SCRIPT_DIR/smo/oam/docker-compose.yaml down
+docker compose -f $SCRIPT_DIR/smo/common/docker-compose.yaml down
diff --git a/solution/users.csv b/solution/users.csv
new file mode 100644 (file)
index 0000000..a67f5e9
--- /dev/null
@@ -0,0 +1,6 @@
+firstName,lastName,email,username,password,role,enabled,force_pwd_change
+Leia,Organa,leia.organa@sdnr.onap.org,leia.organa,Default4SDN!,administration,true,false
+R2,D2,r2.d2@sdnr.onap.org,r2.d2,Default4SDN!,administration,true,false
+Luke,Skywalker,luke.skywalker@sdnr.onap.org,luke.skywalker,Default4SDN!,provision,true,false
+Jargo,Fett,jargo.fett@sdnr.onap.org,jargo.fett,Default4SDN!,supervision,true,false
+Martin,Skorupski,martin.skorupski@highstreet-technologies.com,martin.skorupski,Default4SDN!,administration,true,false
\ No newline at end of file