Skip to content

Commit

Permalink
List all certficates in the UI (#45)
Browse files Browse the repository at this point in the history
Co-authored-by: Matteo Valentini <[email protected]>
  • Loading branch information
stephdl and Amygos authored Nov 8, 2023
1 parent 5a473e9 commit 31e8c77
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 57 deletions.
63 changes: 37 additions & 26 deletions imageroot/actions/get-certificate/validate-output.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,44 @@
"description": "Status of a requested certificate",
"examples": [
{
"fqdn": "example.com",
"obtained": "true"
}
"fqdn": "example.com",
"obtained": "true",
"type": "internal"
}
],
"type": "object",
"oneOf":[
{
"required": [
"fqdn",
"obtained"
]
},
{
"properties": {},
"additionalProperties": false
}
],
"properties": {
"fqdn": {
"type":"string",
"format": "hostname",
"title": "A fully qualified domain name"
"oneOf": [
{
"type": "object",
"properties": {
"fqdn": {
"type": "string",
"format": "hostname",
"title": "A fully qualified domain name"
},
"type": {
"type": "string",
"enum": [
"internal",
"custom",
"route"
],
"title": "must be route, internal or custom"
},
"obtained": {
"type": "boolean",
"title": "true if the certificate was obtained correctly"
}
},
"required": [
"fqdn",
"type",
"obtained"
]
},
"obtained": {
"type":"boolean",
"title": "true if the certificate was obtained correctly"
{
"type": "object",
"additionalProperties": false
}

}
}
]
}
28 changes: 20 additions & 8 deletions imageroot/actions/list-certificates/20readconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,26 @@ try:
traefik_routes = json.load(res)
except urllib.error.URLError as e:
raise Exception(f'Error reaching traefik daemon: {e.reason}') from e

# Gernerate the list of certificates
certificates = [ route['name'].removeprefix('certificate-').removesuffix('@file') for route in traefik_routes
if route['name'].startswith('certificate-') and route['name'].endswith('@file') ]

certificates= []

# list routes and retrieve either main for a simple list
# or name to use it inside the traefik API and list following type and valid acme cert
for route in traefik_routes:
if "certResolver" in route.get("tls", {}) and route['status'] == 'enabled':
domains = route["tls"]["domains"]
if data != None and data.get('expand_list'):
# we do not use fqdn, we use name : certificate-sub.domain.com@file or nextcloud1-https@file
certificates.append(get_certificate({'name': route['name']}))
else:
certificates.append(domains[0]["main"])

# Retrieve custom certificate
if data != None and data.get('expand_list'):
certificates = [ get_certificate({'fqdn': certificate}) for certificate in certificates ]

certificates = certificates + list_custom_certificates()
certificates = certificates + list_custom_certificates()
else:
certificates_custom = []
for item in list_custom_certificates():
certificates_custom.append(item["fqdn"])
certificates = certificates + certificates_custom

json.dump(certificates, fp=sys.stdout)
77 changes: 62 additions & 15 deletions imageroot/actions/list-certificates/validate-output.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,69 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "list-certificates output",
"$id": "http://schema.nethserver.org/samba/list-certificates-output.json",
"$id": "http://schema.nethserver.org/traefik/list-certificates-output.json",
"description": "Return a list of requested certificates fqdn",
"examples": [
["example.com"],
[]
[

],
[
"foo.domain.com",
"nextcloud.domain.com",
"webserver2.domain.com"
],
[
{
"fqdn": "foo.domain.com",
"type": "internal",
"obtained": true
},
{
"fqdn": "nextcloud.domain.com",
"type": "route",
"obtained": true
},
{
"fqdn": "webserver2.domain.com",
"type": "custom",
"obtained": true
}
]
],
"type": "array",
"items": {
"oneOf":[{
"type": "object",
"title": "A certificate expanded"
},
{
"type": "string",
"format": "hostname",
"title": "A fully qualified domain name"
}]
}
}
"anyOf": [
{
"items": {
"type": "string",
"format": "idn-hostname"
}
},
{
"items": {
"type": "object",
"properties": {
"fqdn": {
"type": "string",
"format": "idn-hostname"
},
"type": {
"type": "string",
"enum": [
"internal",
"custom",
"route"
]
},
"obtained": {
"type": "boolean"
}
},
"required": [
"fqdn",
"type",
"obtained"
]
}
}
]
}
23 changes: 15 additions & 8 deletions imageroot/pypkg/get_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,29 @@

def get_certificate(data):
try:
fqdn = data['fqdn']
name = data.get('name','')
fqdn = data.get('fqdn','')
certificate = {}
api_path = os.environ["API_PATH"]
moduleid = os.environ["MODULE_ID"]

# Get the certificate route from the API
with urllib.request.urlopen(f'http://127.0.0.1/{api_path}/api/http/routers/certificate-{fqdn}@file') as res:
traefik_https_route = json.load(res)
# we retrieve route and certificate for list-certificates action
if name != "":
with urllib.request.urlopen(f'http://127.0.0.1/{api_path}/api/http/routers/{name}') as res:
traefik_https_route = json.load(res)

# we retrieve cert from fqdn for backward compatibility (it is an acme certificate)
elif fqdn != "":
with urllib.request.urlopen(f'http://127.0.0.1/{api_path}/api/http/routers/certificate-{fqdn}@file') as res:
traefik_https_route = json.load(res)

# Check if the route is ready to use
if traefik_https_route['status'] == 'disabled':
return {}

certificate['fqdn'] = fqdn
certificate['type'] = 'internal'

certificate['fqdn'] = traefik_https_route['tls']['domains'][0]['main']
# either from internal or route (type could be also custom cert)
certificate['type'] = 'internal' if traefik_https_route['name'].startswith('certificate-') else 'route'
certificate['obtained'] = False

# Open the certificates storage file
Expand All @@ -39,7 +46,7 @@ def get_certificate(data):

# Check if the certificate is present in the storage
for cert in certificates if certificates else []:
if cert['domain']['main'] == data['fqdn']:
if cert['domain']['main'] == certificate['fqdn']:
certificate['obtained'] = True
break

Expand Down
2 changes: 2 additions & 0 deletions tests/20_traefik_certificates_api.robot
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Get invalid cerficate status
${response} = Run task module/traefik1/get-certificate {"fqdn": "example.com"}
Should Be Equal As Strings ${response['fqdn']} example.com
Should Be Equal As Strings ${response['obtained']} False
Should Be Equal As Strings ${response['type']} internal

Get certificate list
${response} = Run task module/traefik1/list-certificates null
Expand All @@ -35,6 +36,7 @@ Get expanded certificate list
${response} = Run task module/traefik1/list-certificates {"expand_list": true}
Should Be Equal As Strings ${response[0]['fqdn']} example.com
Should Be Equal As Strings ${response[0]['obtained']} False
Should Be Equal As Strings ${response[0]['type']} internal

Delete certificate
Run task module/traefik1/delete-certificate {"fqdn": "example.com"}
Expand Down

0 comments on commit 31e8c77

Please sign in to comment.