Skip to content

Commit

Permalink
Merge branch '70-support-did-in-generated-gaia-x-credentials_task3' i…
Browse files Browse the repository at this point in the history
…nto 62-writeupdate-documentation
  • Loading branch information
anjastrunk committed Aug 27, 2024
2 parents 71bfc1f + f3d7bd4 commit ee536e2
Show file tree
Hide file tree
Showing 27 changed files with 1,011 additions and 3,315 deletions.
39 changes: 36 additions & 3 deletions config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
################################################################
########################################################################################
# Mandatory attributes required by Gaia-X
################################################################
########################################################################################
Credentials:
# absolute path to private key to sign verifiable credentials
key: path-to-private-key
# verification method used to check proof of verifiable credential. Method must be published within DID document of CSP
verification-method: did:web:example.com#JWK2020-X509-0
# base url for generated credentials
base_url: https://example.com
# URL did resolver web service
did_resolver: https://uniresolver.io/1.0/identifiers

CPS:
did: did:web:example.com
legal-name: Example Corp
# allowed values are country codes according to ISO 3166-2 alpha2, alpha-3 or numeric format.
legal-address-country-code: DE-SN
# allowed values are country codes according to ISO 3166-2 alpha2, alpha-3 or numeric format.
headquarter-address-country-code: DE-SN
# list of registration numbers. At least one registration number must be given. Each key MUST can only be set once.
registration_numbers:
# CSP VAT number
vat-id: DE123456789
# CSP LEI code
lei-code: 123456789
# CSP local registration number
local-req-number: 123456789
# CSP EORI code
eori: 123456789
# CSP EUID code
euid: 123456789

IaaS:
did: did:web:example.com:iaas
# URL to a document containing terms and conditions of service offering
terms-and-conditions:
- www.example.com/tan1
- www.example.com/tan2
Expand All @@ -20,6 +46,13 @@ IaaS:
# Allowed values are MIME types
format-type: "plain"

# Endpoints of Gaia-X Digital Clearing House
gxdch:
notary-service: https://registrationnumber.notary.lab.gaia-x.eu/v1-staging
compliance-service: https://compliance.lab.gaia-x.eu/v1-staging
registry-service: https://registry.lab.gaia-x.eu/v1-staging


################################################################
# CAUTION: Do not change these values, unless you want to overwrite default behaviour
################################################################
Expand Down
257 changes: 231 additions & 26 deletions generator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,81 +11,286 @@
"""

import json
import os
import sys
from datetime import datetime, timezone
from typing import List

import click
import openstack as os
import openstack as o_stack
import yaml
from openstack.connection import Connection

import generator.common.const as const
import generator.common.json_ld as json_ld
from generator.common import credentials, crypto
from generator.common.config import Config
from generator.discovery.csp_generator import CspGenerator
from generator.discovery.gxdch_services import (ComplianceService,
RegistryService)
from generator.discovery.openstack.openstack_discovery import \
OpenstackDiscovery

SHAPES_FILE_FORMAT = "turtle"
DATA_FILE_FORMAT = "json-ld"

VC_NAME_LOOKUP = {
"lp": "Legal Person",
"lrn": "Legal Registration Number",
"tandc": "Gaia-X Terms and Conditions",
"cs_csp": "GXDCH Compliance Service",
"cs_so": "GXDCH Compliance Service",
"so": "Service Offering",
"vmso": "Virtual Machine Service Offering",
}


@click.group()
def cli_commands():
pass


@click.command()
@click.option(
"--auto-sign/--no-auto-sign",
default=False,
help="Sign Gaia-X Terms and Conditions, automatically, without asking for permission on screen.",
)
@click.option(
"--out-dir",
default=".",
help="Path to output directory.",
)
@click.option(
"--config",
default="config/config.yaml",
help="Path to Configuration file for SCS GX Credential Generator.",
)
@click.option("--timeout", default=12, help="Timeout for API calls in seconds")
@click.option("--timeout", default=24, help="Timeout for API calls in seconds")
@click.argument("cloud")
def openstack(cloud, timeout, config):
"""Generates Gaia-X Credentials for openstack cloud CLOUD.
def openstack(cloud, timeout, config, out_dir, auto_sign):
"""Generates Gaia-X Credentials for CSP And OpenStack cloud CLOUD.
CLOUD MUST refer to a name defined in Openstack's configuration file clouds.yaml."""
with open(config, "r") as config_file:
conf = Config(yaml.safe_load(config_file))

if not auto_sign and not _are_gaiax_tandc_signed(conf):
# user did not agree Gaia-X terms and conditions, we have to abort here
print("Gaia-X terms and conditions were not signed - process aborted!")
return

# create Gaia-X Credentials for CSP
csp_gen = CspGenerator(conf=conf)
csp_vcs = csp_gen.generate()

# create Gaia-X Credentials for OpenStack
so_vcs = create_vmso_vcs(
conf=conf,
cloud=cloud,
csp_vcs=csp_vcs,
timeout=timeout,
)

vcs = {**csp_vcs, **so_vcs}
_print_vcs(vcs, out_dir)


@click.command()
def kubernetes():
"""Generates Gaia-X Credentials for CPS and kubernetes."""
pass


# def load_file(filepath, file_format=DATA_FILE_FORMAT):
# """Load file in a given format"""
# graph = rdflib.Graph()
# graph.parse(filepath, format=file_format)
# return graph

# init Openstack Connections
conn = os.connect(cloud=cloud, timeout=timeout, api_timeout=timeout * 1.5 + 4)
@click.command()
@click.option(
"--auto-sign/--no-auto-sign",
default=False,
help="Sign Gaia-X Terms and Conditions, automatically, without asking for permission on screen.",
)
@click.option(
"--out-dir",
help="Path to output directory.",
)
@click.option(
"--config",
default="config/config.yaml",
help="Path to Configuration file for SCS GX Credential Generator.")
def csp(config, out_dir, auto_sign):
"""Generate Gaia-X Credential for CPS."""
# load config file
with open(config, "r") as config_file:
conf = Config(yaml.safe_load(config_file))

if not auto_sign and not _are_gaiax_tandc_signed(conf):
# user did not agree Gaia-X terms and conditions, we have to abort here
print("Gaia-X terms and conditions were not signed - process aborted!")
return
vcs = CspGenerator(conf).generate()
_print_vcs(vcs, out_dir)


def init_openstack_connection(cloud: str, timeout: int = 12) -> Connection:
"""
Init connection to OpenStack cloud.
@param cloud: name of OpenStack cloud to be connected.
@param timeout: time, after connection is initiated a second time.
@return: OpenStacl connection.
"""
try:
conn = o_stack.connect(cloud=cloud, timeout=timeout, api_timeout=timeout * 1.5 + 4)
conn.authorize()
except Exception:
print("INFO: Retry connection with 'default' domain", file=sys.stderr)
conn = os.connect(
conn = o_stack.connect(
cloud=cloud,
timeout=timeout,
api_timeout=timeout * 1.5 + 4,
default_domain="default",
project_domain_id="default",
)
conn.authorize()
return conn

# generate Gaia-X Credentials
with open(config, "r") as config_file:
# init everything
config_dict = yaml.safe_load(config_file)
os_cloud = OpenstackDiscovery(conn, Config(config_dict))

# run discovery
creds = os_cloud.discover()
def create_vmso_vcs(conf: Config, cloud: str, csp_vcs: List[dict], timeout: int = 12) -> dict[dict]:
"""
Create Gaia-X Credentials for Virtual Machine Service Offering. This means
- Gaia-X Credential of OpenStack Cloud as ServiceOffering with mandatory attributes
- Gaia-X Credential of OpenStack Cloud as VirtualMachineServiceOffering
- Gaia-X Credential of GXDCH Compliance Service, attesting complaince of OpenStack cloud description with Gaia-X rules.
@param conf: configuration settings for creation process.
@param cloud: OpenStack ncloud name.
@param csp_vcs: Gaia-X Credentials of Cloud Service Provider.
@param timeout: timeout for connection to OpenStack cloud. If timeout expires, connection is initialed a second time.
@return: A list of Gaia-X Credentials describing given OpenStack cloud.
"""
csp = conf.get_value([const.CONFIG_CSP])
# iaas = conf.get_value([const.CONFIG_IAAS]) not yet used, as Gaia-X "abuses" id attribute of Verifiable Credentials
cred_settings = conf.get_value([const.CONFIG_CRED])

props = json_ld.get_json_ld_context()
props["@graph"] = creds
print(json.dumps(props, indent=4, default=json_ld.to_json_ld))
# init services
conn = init_openstack_connection(cloud=cloud, timeout=timeout)
compliance = ComplianceService(conf.get_value([const.CONST_GXDCH, const.CONST_GXDCH_COMP]))
discovery = OpenstackDiscovery(conn=conn, config=conf)

# run openstack discovery and build Gaia-X Credential for Virtual Machine Service Offering
print('Create VC of type "gx:VirtualMachineServiceOffering"...', end='')
vm_offering = discovery.discover()
vmso_vc = {
'@context': [const.VC_CONTEXT, const.JWS_CONTEXT, const.REG_CONTEXT],
'type': "VerifiableCredential",
'id': cred_settings[const.CONFIG_CRED_BASE_CRED_URL] + "/vmo.json",
'issuer': csp['did'],
'issuanceDate': str(datetime.now(tz=timezone.utc).isoformat()),
'credentialSubject': json.loads(json.dumps(vm_offering, default=json_ld.to_json_ld)),
}
vmso_vc_signed = crypto.sign_cred(cred=vmso_vc,
key=crypto.load_jwk_from_file(cred_settings[const.CONFIG_CRED_KEY]),
verification_method=cred_settings[const.CONFIG_CRED_VER_METH])
print('ok')

@click.command()
def kubernetes():
"""Generates Gaia-X Credentials for kubernetes."""
pass
# build Gaia-X Credential for Service Offering
print('Create VC of type "gx:ServiceOffering"...', end='')
so_vc = {
'@context': [const.VC_CONTEXT, const.JWS_CONTEXT, const.REG_CONTEXT],
'type': "VerifiableCredential",
'id': cred_settings[const.CONFIG_CRED_BASE_CRED_URL] + "/so.json",
'issuer': csp['did'],
'issuanceDate': str(datetime.now(tz=timezone.utc).isoformat()),
'credentialSubject': {
"type": "gx:ServiceOffering",
"id": cred_settings[const.CONFIG_CRED_BASE_CRED_URL] + "/so.json#subject", # iaas['did'],
"gx:providedBy": {
'id': csp_vcs['lp']['credentialSubject']['id']
},
"gx:termsAndConditions": [
{'gx:URL': s_tac.url, 'gx:hash': s_tac.hash}
for s_tac in vm_offering.serviceOfferingTermsAndConditions],
"gx:policy": vm_offering.servicePolicy,
"gx:dataAccountExport": {
"gx:requestType": vm_offering.dataAccountExport.requestType.code.text,
"gx:accessType": vm_offering.dataAccountExport.accessType.code.text,
"gx:formatType": "application/" + vm_offering.dataAccountExport.formatType.code.text
}
}
}

# sign service offering credential
so_vc_signed = crypto.sign_cred(cred=so_vc,
key=crypto.load_jwk_from_file(cred_settings[const.CONFIG_CRED_KEY]),
verification_method=cred_settings[const.CONFIG_CRED_VER_METH])
print('ok')

# Request Gaia-X Compliance Credential for Service Offering
print('Request VC of type "gx:compliance" for Service Offering at GXDCH Compliance Service...', end='')
vp = credentials.convert_to_vp(creds=[csp_vcs['tandc'], csp_vcs['lrn'], csp_vcs['lp'], so_vc_signed])
comp_vc = compliance.request_compliance_vc(vp,
cred_settings[const.CONFIG_CRED_BASE_CRED_URL] + "/so_compliance.json")

print('ok')
return {'so': so_vc, 'cs_so': json.loads(comp_vc), 'vmso': vmso_vc_signed, 'vp_so': vp}


def _get_timestamp():
dt = datetime.now() # for date and time
# ts_1 = datetime.timestamp(dt) # for timestamp
return dt.strftime('%Y-%m-%d_%H-%M-%S')


def _print_vcs(vcs: dict, out_dir: str = "."):
if not os.path.isdir(out_dir):
raise NotADirectoryError(out_dir + " is not a directory or does not exit!")

ts = _get_timestamp()
for key in vcs:
vc_path = os.path.join(out_dir, key + "_" + ts + ".json")
with open(vc_path, "w") as vc_file:
if key == 'vp_csp':
print(
"Write Verifiable Presentation of Cloud Service Provider to be verified at GXDCH Compliance Service to " + str(
vc_path))
vc_file.write(json.dumps(vcs[key], indent=2))
elif key == 'vp_so':
print(
"Write Verifiable Presentation of Service Offering to be verified at GXDCH Compliance Service to " + str(
vc_path))
vc_file.write(json.dumps(vcs[key], indent=2))
else:
print("Write Gaia-X Credential for " + VC_NAME_LOOKUP[key] + " to " + str(vc_path))
vc_file.write(json.dumps(vcs[key], indent=2))


def _are_gaiax_tandc_signed(conf: Config) -> bool:
reg = RegistryService(conf.get_value([const.CONST_GXDCH, const.CONST_GXDCH_REG]))
tand = reg.get_gx_tandc()

print("Do you agree Gaia-X Terms and conditions version " + tand['version'] + ".")
print()
print("-------------------------- Gaia-X Terms and Conditions --------------------------------------------")
print(tand['text'])
print("-------------------------- ------------------------------------------------------------------------")
print()
print("Please type 'y' for 'I do agree' and 'n' for 'I do not agree': ")

resp = input()
while resp.lower() not in ['y', 'n']:
print("Please type 'y' for 'I do agree' and 'n' for 'I do not agree: '")
resp = input()

if resp.lower() == 'y':
return True
return False

# def load_file(filepath, file_format=DATA_FILE_FORMAT):
# """Load file in a given format"""
# graph = rdflib.Graph()
# graph.parse(filepath, format=file_format)
# return graph

cli_commands.add_command(openstack)
cli_commands.add_command(kubernetes)
cli_commands.add_command(csp)

if __name__ == "__main__":
cli_commands()
13 changes: 12 additions & 1 deletion generator/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@ def _get_value(config, keys: List[str]):


class Config:
def __init__(self, config):
"""Wrapper class for all configuration settings. Configuration settings are stored in yaml file on drive and
imported as a nested dictionary."""
def __init__(self, config: dict):
self.config = config

def get_value(self, keys: List[str]):
"""
Return configuration value. Config settings are stored as yaml and imported as nested dict.
E.g. { 'key1': {'key2': {'key3': 'foo'}}}
The list of keys are required to step down throught nested dicts to requested value.
E.g. ['key1', 'key2', 'key3'] returns 'foo'
@param keys: list of keys
@return: value
"""
try:
return _get_value(self.config, keys)
except KeyError:
Expand Down
Loading

0 comments on commit ee536e2

Please sign in to comment.