Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Gaia-X compliant credential for CSP's as LegalPerson and OpenStack Cloud as "ServiceOffering" #96

Merged
merged 52 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0543cef
Add GXDCH services
anjastrunk Jun 10, 2024
4fde556
Add legal person vc creation
anjastrunk Jun 12, 2024
b6fa3c1
Create mandatory vc
anjastrunk Jun 12, 2024
42f3a0a
Add current status
anjastrunk Jun 14, 2024
b4d8304
Commit current working state
anjastrunk Aug 7, 2024
96dbdea
Add generation of valid GX Credentials for CSPs
anjastrunk Aug 8, 2024
7eaeade
Generate Gaia-X Credentials for Service OfferingsW
anjastrunk Aug 9, 2024
c000a45
Update requirements
anjastrunk Aug 9, 2024
07c29b4
Remove outdated files
anjastrunk Aug 9, 2024
d87bf1e
Fix test cli tests
anjastrunk Aug 13, 2024
7c5ab04
Fix tests
anjastrunk Aug 13, 2024
316dbbb
Fix tests and refactor source code
anjastrunk Aug 13, 2024
4fefc2f
Update config
anjastrunk Aug 13, 2024
5ecc73b
Fix flake errors
anjastrunk Aug 14, 2024
f0d36fb
Bump certifi from 2024.2.2 to 2024.7.4 (#100)
dependabot[bot] Aug 7, 2024
86fd75c
Bump urllib3 from 2.2.1 to 2.2.2 (#99)
dependabot[bot] Aug 7, 2024
d44b2f7
Bump braces from 3.0.2 to 3.0.3 (#101)
dependabot[bot] Aug 8, 2024
555c9b2
Print CPS credentials
anjastrunk Aug 16, 2024
5683b84
Improve output
anjastrunk Aug 19, 2024
4bc2669
Fix unit tests
anjastrunk Aug 20, 2024
4c1bd44
Manage optional properties
anjastrunk Aug 20, 2024
76c5ecc
Increase code coverage
anjastrunk Aug 20, 2024
8b37808
Increase code coverage
anjastrunk Aug 20, 2024
53e4c61
Fix python lint errors
anjastrunk Aug 20, 2024
dd5d38a
Increase code covrage
anjastrunk Aug 20, 2024
d3d3d4c
Fix python lint errors§
anjastrunk Aug 20, 2024
96615fc
Update generator/cli.py
anjastrunk Aug 21, 2024
2cd680c
Update generator/cli.py
anjastrunk Aug 21, 2024
7e942c1
Update generator/cli.py
anjastrunk Aug 21, 2024
f8a68c1
Update generator/cli.py
anjastrunk Aug 21, 2024
ffd63af
Update generator/discovery/openstack/openstack_discovery.py
anjastrunk Aug 21, 2024
e3e6f37
Update generator/discovery/openstack/openstack_discovery.py
anjastrunk Aug 21, 2024
90705c6
Integrate review comments
anjastrunk Aug 21, 2024
d41309a
Fix typo
anjastrunk Aug 21, 2024
bc8a8db
Fix python lint errors
anjastrunk Aug 21, 2024
28260a6
Merge branch 'main' into 70-support-did-in-generated-gaia-x-credentia…
anjastrunk Aug 21, 2024
63a996b
Resolve review comments
anjastrunk Aug 26, 2024
8c0c67b
Update generator/cli.py
anjastrunk Aug 27, 2024
1aef4ca
Update generator/discovery/csp_generator.py
anjastrunk Aug 27, 2024
669b8b1
Update generator/discovery/csp_generator.py
anjastrunk Aug 27, 2024
87d87cd
Update generator/discovery/csp_generator.py
anjastrunk Aug 27, 2024
0240b17
Update tests/test_csp_generator.py
anjastrunk Aug 27, 2024
f2b2e67
Update tests/test_csp_generator.py
anjastrunk Aug 27, 2024
3aa020e
Update tests/test_csp_generator.py
anjastrunk Aug 27, 2024
f3d7bd4
Output compliance credential for both CSP and Service Offering
anjastrunk Aug 27, 2024
a4e24d3
Update generator/common/credentials.py
anjastrunk Aug 27, 2024
30b64f6
Update generator/common/const.py
anjastrunk Aug 28, 2024
cb029ff
Update config/config.yaml
anjastrunk Aug 28, 2024
5a8ceb2
Update generator/common/const.py
anjastrunk Aug 28, 2024
6552769
Update generator/cli.py
anjastrunk Aug 28, 2024
3c06130
Update generator/cli.py
anjastrunk Aug 28, 2024
829734f
Fix os_image.os_distro is None
anjastrunk Aug 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
################################################################
########################################################################################
# Mandatory attributes required by Gaia-X
################################################################
CPS:
########################################################################################
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

CSP:
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 CSP 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 CSP."""
# 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(
anjastrunk marked this conversation as resolved.
Show resolved Hide resolved
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='')
anjastrunk marked this conversation as resolved.
Show resolved Hide resolved
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))
anjastrunk marked this conversation as resolved.
Show resolved Hide resolved
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
Loading