Skip to content

Commit

Permalink
Merge pull request #8 from Sphereon-Opensource/develop
Browse files Browse the repository at this point in the history
First release for testing
  • Loading branch information
nklomp authored Jan 27, 2023
2 parents a2cfb2e + 6b69e3f commit 32981e8
Show file tree
Hide file tree
Showing 19 changed files with 486 additions and 146 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
__These packages are in a very early development stage. Breaking changes without notice will occur at this
point!__

**WARNING: This Gaia-X agent is compatible only with a non-official Gaia-X compliance service with Verifiable Presentation support**
**WARNING: This Gaia-X agent is compatible only with a non-official Gaia-X compliance service with Verifiable
Presentation support**
---

# Gaia-X agent
Expand All @@ -37,7 +38,8 @@ If you quickly want to test out the agent features, we suggest the [CLI](package

## Create X.509 keys and get SSL certificate

You will first need to have an existing X.509 EV SSL certificate or create a new one. [This document](./docs/X509-setup.md)
You will first need to have an existing X.509 EV SSL certificate or create a new
one. [This document](./docs/X509-setup.md)
explains how to setup a new X.509 certificate. Without following the steps in the document you cannot be onboarded as
Gaia-X participant.

Expand All @@ -46,16 +48,23 @@ Gaia-X participant.
For now the [CLI](packages/gx-agent-cli/README.md) is the only documented way to setup the agent. In the future the
other scenario's will be described as well.

## Onboarding documentation and CLI documentation

The [CLI Documentatation](./packages/gx-agent-cli/README.md) explains all the commands available in the CLI. However if
you would like to follow a more structured process on how you can onboard using the Agent/CLI, you can also
read [this PDF](./docs/Gaia-X%20and%20FMA%20-%20Onboarding%20Process.pdf) document, which has additional information,
and takes you through the process in consecutive order.

# Developers

This is mono repository, with packages that handle steps for creating Gaia-X compliant Entities like self-descriptions
compatible with [Veramo](https://veramo.io) modules.

This mono repo has the following packages:

- compliance-client
- [gx-agent](./packages/gx-agent)
- an agent managing GX credentials, presentations and compliance service
- compliance-cli
- [gx-agent-cli](./packages/gx-agent-cli)
- CLI support for the agent

## Building and testing
Expand Down
Binary file added docs/Gaia-X and FMA - Onboarding Process.pdf
Binary file not shown.
139 changes: 99 additions & 40 deletions packages/gx-agent-cli/README.md

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions packages/gx-agent-cli/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'cross-fetch/polyfill'
import { program } from 'commander'
import { getAgent } from '@sphereon/gx-agent'
import { createAgentConfig, getDefaultAgentFile, showConfig } from '@sphereon/gx-agent/dist/utils/config-utils'
import { createAgentConfig, getDefaultAgentFile, getConfigAsString } from '@sphereon/gx-agent/dist/utils/config-utils'

const config = program.command('config').description('Agent configuration')

Expand All @@ -10,17 +10,17 @@ config
.description('Create default agent config')
.option(
'-l, --location <string>',
'Config file name and location (defaults to `home`: ' + getDefaultAgentFile() + '. Valid values are `home` and `cwd`. cwd means the current working directory)',
'./agent.yml'
'Config file name and location (defaults to `home`: ' +
getDefaultAgentFile() +
'. Valid values are `home` and `cwd`. cwd means the current working directory)',
`home (${getDefaultAgentFile()})`
)
// .option('--template <string>', 'Use template (default,client)', 'default')

.action(async (options) => {
const { location } = options

const path = location && location.toLowerCase() === 'cwd' ? './agent.yml' : getDefaultAgentFile()

createAgentConfig(path)
await createAgentConfig(path)
})

config
Expand All @@ -32,7 +32,7 @@ config
.option('-s, --show', 'Show the configuration file')
.action(async (options) => {
if (options.show) {
console.log(showConfig(options.filename))
console.log(getConfigAsString(options.filename))
}

const agent = await getAgent({ path: options.filename })
Expand All @@ -42,7 +42,7 @@ config
)
} else {
// @ts-ignore
if (typeof agent[options.method] !== 'function'/* && options.method !== 'execute'*/) {
if (typeof agent[options.method] !== 'function') {
console.error(
`The agent was created using the config command, but the 'agent.${options.method}()' method is not available. Make sure the plugin that implements that method is installed.`
)
Expand Down
155 changes: 120 additions & 35 deletions packages/gx-agent-cli/lib/ecosystem.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,137 @@
import { program } from 'commander'
import { GxEntityType } from './types'
import { printTable } from 'console-table-printer'
import { getAgent } from '@sphereon/gx-agent'
import { EcosystemConfig, getAgent, normalizeEcosystemConfigurationObject } from '@sphereon/gx-agent'
import { VerifiableCredential } from '@veramo/core'
import {
addEcosystemConfigObject,
assertValidEcosystemConfigObject,
deleteEcosystemConfigObject,
getAgentConfigPath,
getEcosystemConfigObject,
getEcosystemConfigObjects,
} from '@sphereon/gx-agent/dist/utils/config-utils'
import fs from 'fs'

const ecosystem = program.command('ecosystem').description('gx-participant ecosystem')
const ecosystem = program.command('ecosystem').description('Ecosystem specific commands')

ecosystem
.command('add')
.description('Adds a new Gaia-x ecosystem to the client')
.requiredOption('-n, --name <string>', 'ecosystem name')
.requiredOption('-url, --ecosystem-url <string>', 'gaia-x ecosystem server address')
.action(async (cmd) => {
const agent = await getAgent()
const id = await agent.dataStoreSaveMessage({
//todo: create an entity here instead of using message
message: {
id: cmd.name,
type: GxEntityType.ecosystem,
createdAt: new Date().toUTCString(),
data: {
name: cmd.name,
'ecosystem-url': cmd.ecosystemUrl,
.alias('update')
.description('Adds or updates a Gaia-x ecosystem to the client')
.argument(
'<name>',
'ecosystem name. Please ensure to use use quotes in case the name contains spaces. We suggest to use a shorthand/abbreviation for the name, and to use the description for the fullname'
)
.argument('<url>', 'gaia-x ecosystem server address')
.option('-d, --description <string>', 'Description')
.action(async (name, url, cmd) => {
//just to verify the agent loads
await getAgent()

const configPath = getAgentConfigPath()
const existingConfig = getEcosystemConfigObject(configPath, name)
const ecosystemConfig: EcosystemConfig = {
name,
url,
description: cmd.description,
}
assertValidEcosystemConfigObject(ecosystemConfig)
addEcosystemConfigObject(configPath, ecosystemConfig)
//just to verify the agent loads
await getAgent()

if (existingConfig) {
console.log(`Existing ecosystem ${name} has been updated in your agent configuration: ${getAgentConfigPath()}`)
printTable([
{
version: 'previous',
name: existingConfig.name,
url: existingConfig.url,
description: existingConfig.description,
},
},
})
printTable([{ id }])
{ version: 'new', name, url, description: cmd.description },
])
} else {
console.log(`New ecosystem ${name} has been added to your agent configuration: ${getAgentConfigPath()}`)
printTable([{ name, url, description: cmd.description }])
}
})

ecosystem
.command('list')
.description('Lists all Gaia-x ecosystems known to the agent (that are in the configuration)')
.action(async (cmd) => {
const configPath = getAgentConfigPath()
const ecosystems = getEcosystemConfigObjects(configPath)

if (!ecosystems || ecosystems.length === 0) {
console.log('No ecosystems currently configured. You can create one using the "gx-agent ecosystem add" command')
} else {
printTable(ecosystems)
}
})

ecosystem
.command('delete')
.description('Deletes a Gaia-x ecosystem from the agent configuration')
.argument('<name>', 'ecosystem name')
.action(async (name) => {
//just to verify the agent loads
await getAgent()

const configPath = getAgentConfigPath()
const existing = getEcosystemConfigObject(configPath, name)
if (!existing) {
console.log(`No ecosystem with name "${name}" was found in the agent config: ${configPath}`)
} else {
deleteEcosystemConfigObject(configPath, name)

//just to verify the agent loads
await getAgent()

printTable([{ ...existing }])
console.log(`Ecosystem ${name} has been deleted from your agent configuration: ${getAgentConfigPath()}`)
}
})

ecosystem
.command('submit')
.description('Onboards the participant to the new ecosystem')
.option('-sid, --sd-id <string>', 'id of your self-description')
.option('-cid, --compliance-id <string>', '')
// .option('-eurl, --ecosystem-url <string>', 'URL of gx-compliance server')
.option('-e, --ecosystem <string>', 'alias of your ecosystem')
.action(async (cmd) => {
.argument('<name>', 'The ecosystem name (has to be available in your configuration)')
.option('-sid, --sd-id <string>', 'ID of your self-description verifiable credential')
.option('-sf, --sd-file <string>', 'File containing your self-description verifiable credential')
.option('-cid, --compliance-id <string>', 'ID of your compliance credential')
.option('-cf, --compliance-file <string>', 'File containing your compliance credential')
.action(async (name, cmd) => {
const agent = await getAgent()
if (!cmd.sdId && !cmd.sdFile) {
throw Error('Verifiable Credential ID or file for self-description need to be selected. Please check parameters')
}
if (!cmd.complianceId && !cmd.complianceFile) {
throw Error('Verifiable Credential ID or file for self-description need to be selected. Please check parameters')
}
try {
const agent = await getAgent()
const selfDescriptionId = cmd.sdId
const complianceId = cmd.complianceId

//fixme: Does not take ecosystem into account at all
const selfDescription = await agent.onboardParticipantWithCredentialIds({
selfDescriptionId,
complianceId,
const selfDescriptionVC = cmd.sdFile
? (JSON.parse(fs.readFileSync(cmd.sdFile, 'utf-8')) as VerifiableCredential)
: await agent.dataStoreGetVerifiableCredential({ hash: cmd.sdId })
const complianceVC = cmd.complianceFile
? (JSON.parse(fs.readFileSync(cmd.complianceFile, 'utf-8')) as VerifiableCredential)
: await agent.dataStoreGetVerifiableCredential({ hash: cmd.complianceId })

const agentPath = getAgentConfigPath()
const ecosystemConfig: EcosystemConfig | undefined = getEcosystemConfigObject(agentPath, name)
if (!ecosystemConfig) {
console.error(`Couldn't find the ecosystem: ${name}`)
return
}
const selfDescription = await agent.onboardParticipantOnEcosystem({
ecosystemUrl: normalizeEcosystemConfigurationObject(ecosystemConfig).url,
selfDescriptionVC,
complianceVC,
})
console.log(JSON.stringify(selfDescription, null, 2))
printTable([{ ...selfDescription }])
} catch (e: unknown) {
console.error(e)
} catch (e: any) {
console.error(e.message)
}
})
15 changes: 2 additions & 13 deletions packages/gx-agent-cli/lib/participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,14 @@ import {
} from '@sphereon/gx-agent'

const participant = program.command('participant').description('Participant commands')
// const compliance = participant.command('compliance').description('Compliance and self-descriptions')
const sd = participant.command('sd').alias('self-description').description('Participant self-description commands')
/*
compliance
.command('compliance')
.command('status')
.description('shows the compliance status of the Participant')
.option('-sid, --sd-id <string>', 'id of your self-description')
.action(async (cmd) => {
console.error('Feature not implemented yet')
})
*/

sd.command('submit')
.description(
'submits a self-description file to the compliance service. This can either be an input file (unsigned credential) from the filesystem, or a signed self-description stored in the agent'
)
.option('-if, --sd-input-file <string>', 'Unsigned self-description input file location')
.option('-id, --sd-id <string>', 'id of a signed self-description stored in the agent')
.option('-sif, --sd-input-file <string>', 'Unsigned self-description input file location')
.option('-sid, --sd-id <string>', 'id of a signed self-description stored in the agent')
.option('-s, --show', 'Show self descriptions')
.action(async (cmd) => {
try {
Expand Down
36 changes: 26 additions & 10 deletions packages/gx-agent-cli/lib/vc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@ vc.command('issue')
const agent = await getAgent()
try {
const credential: CredentialPayload = JSON.parse(fs.readFileSync(cmd.inputFile, 'utf-8')) as CredentialPayload
const did = cmd.did ? await asDID(cmd.did) : typeof credential.issuer === 'string' ? credential.issuer : credential.issuer.id
const did = cmd.did
? await asDID(cmd.did)
: typeof credential.issuer === 'string'
? credential.issuer
: credential.issuer
? credential.issuer.id
: await asDID()
const id = await agent.didManagerGet({ did })
const didDoc = await exportToDIDDocument(id)
const url = `https://${convertDidWebToHost(did)}`
if (!credential.issuer) {
credential.issuer = did
}

nock.cleanAll()
nock(url)
Expand Down Expand Up @@ -52,6 +61,7 @@ vc.command('issue')
}
} catch (e: any) {
console.error(e.message)
throw e
} finally {
nock.cleanAll()
}
Expand Down Expand Up @@ -95,28 +105,34 @@ vc.command('verify')
.option('--show', 'Print the Verifiable Credential to console')
.action(async (cmd) => {
const agent = await getAgent()
try {
if (!cmd.inputFile && !cmd.vcId) {
throw Error('Either a Verifiable Credential input file or the id of a stored Verifiable Credential needs to be supplied')
} else if (cmd.inputFile && cmd.vcId) {
throw Error('Cannot both have a Verifiable Credential input file and the id of a stored Verifiable Credential')
}
if (!cmd.inputFile && !cmd.vcId) {
throw Error('Either a Verifiable Credential input file or the id of a stored Verifiable Credential needs to be supplied')
} else if (cmd.inputFile && cmd.vcId) {
throw Error('Cannot both have a Verifiable Credential input file and the id of a stored Verifiable Credential')
}

try {
const verifiableCredential: VerifiableCredential = cmd.inputFile
? (JSON.parse(fs.readFileSync(cmd.inputFile, 'utf-8')) as VerifiableCredential)
: await agent.dataStoreGetVerifiableCredential({ hash: cmd.vcId })

const issuer = typeof verifiableCredential.issuer === 'string' ? verifiableCredential.issuer : verifiableCredential.issuer.id
const did = cmd.did
? await asDID(cmd.did)
: typeof verifiableCredential.issuer === 'string'
? verifiableCredential.issuer
: verifiableCredential.issuer
? verifiableCredential.issuer.id
: await asDID()
let id: IIdentifier | undefined
try {
id = await agent.didManagerGet({ did: issuer })
id = await agent.didManagerGet({ did })
} catch (e) {
// DID not hosted by us, which is fine
}

if (id) {
const didDoc = await exportToDIDDocument(id)
const url = `https://${convertDidWebToHost(issuer)}`
const url = `https://${convertDidWebToHost(did)}`
nock.cleanAll()
nock(url)
.get(`/.well-known/did.json`)
Expand Down
4 changes: 2 additions & 2 deletions packages/gx-agent-cli/lib/vp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ vp.command('issue')
.option('-f, --vc-files <string...>', 'File(s) containing Verifiable Credentials')
.option('-c, --challenge <string>', 'Use a challenge')
.option('-p, --persist', 'Persist the presentation. If not provided the presentation will not be stored in the agent')
.option('--show', 'Print the Verifiable Presentation to console')
.option('-s, --show', 'Print the Verifiable Presentation to console')

.action(async (cmd) => {
const agent = await getAgent()
if (cmd.vcFiles && !cmd.vcIds) {
if (!cmd.vcFiles && !cmd.vcIds) {
throw Error('Verifiable Credential IDs or files need to be selected. Please check parameters')
}
try {
Expand Down
Loading

0 comments on commit 32981e8

Please sign in to comment.