Skip to content

Commit

Permalink
jwt headers documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
david-blasby committed Dec 10, 2024
1 parent 4bb2d7e commit e350bcb
Show file tree
Hide file tree
Showing 17 changed files with 701 additions and 0 deletions.
Empty file added docs/manual/README.md
Empty file.
67 changes: 67 additions & 0 deletions docs/manual/docs/GN4-Integration/auth-json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Simple Authentication between GN4 and GN5 with JSON

> [!WARNING]
> If your GN4 is accessible through other means than GN5 (i.e. via the internet or intranet), use the [JWT version](auth-jwt.md).
> A malicious actor can add a header to the requests and directly communicate with GN4. GN4 would then trust this header.
### GN5 setup

In `application.yml` (cloud: gateway: mvc), add the "filters" (at the bottom):
```yaml
cloud:
gateway:
mvc:
routes:
- id: geonetwork_proxy_redirect
uri: ${geonetwork.openapi.url}
predicates:
- Path=/geonetwork/proxy
filters:
- RewritePath=/geonetwork/(?<url>.*), /api/$\{url}
- id: geonetwork_route
uri: ${geonetwork.core.url}
predicates:
- Path=/geonetwork/**
filters:
- addSimpleGn4SecurityHeader=gn5.to.gn4.trusted.json.auth
```
`gn5.to.gn4.trusted.json.auth` is the name of the header that will be attached to the requests so auth is recognized by GN4's JWT Headers Security module (see below).

This header will, typically, look like this:

```json
{
"username":"david"
}
```


### GN4 setup

1. You will need the JWT Headers security model (available in the latest GN4)
2. Set it up with the environment variables, see below.
3. Run with JWT Headers enabled `-Dgeonetwork.security.type=jwt-headers`
4. GN4 must **NOT** be accessible other than via the GN4 Gateway proxy. GN4 is trusting headers so they must be removed.

- If GN4 is available, you **MUST** remove the `gn5.to.gn4.trusted.json.auth` header from incoming requests (typically done with your webserver)

```sh
JWTHEADERS_UserNameHeaderName=gn5.to.gn4.trusted.json.auth
JWTHEADERS_UserNameFormat=JSON
JWTHEADERS_UserNameJsonPath=username
JWTHEADERS_JwtHeaderRoleSource=DB
JWTHEADERS_UpdateProfile=false
JWTHEADERS_UpdateGroup=false
```

Environment variables meaning:

* `JWTHEADERS_UserNameHeaderFormat` - name of the header to use (should be the same as your GN5 `application.yml` filter config)
* `JWTHEADERS_UserNameFormat` - should be JSON (the header is a JSON string)
* `JWTHEADERS_UserNameJsonPath` - where in the JSON is the username
* `JWTHEADERS_JwtHeaderRoleSource` - this should be `DB` (GN DB will manage user-profile-groups)
* `JWTHEADERS_UpdateProfile` - JWT Headers should **NOT** update the DB profiles (user allowed to edit in GUI)
* `JWTHEADERS_UpdateGroup` - JWT Headers should **NOT** update the DB user-groups (user allowed to edit in GUI)


See the GN JWT Headers documentation for more info.
135 changes: 135 additions & 0 deletions docs/manual/docs/GN4-Integration/auth-jwt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Secure Authentication between GN4 and GN5 with JWT


## Securing GN5 to GN4 with signed JWT


A more secure way to secure send information to GN4 is to use signed JWT. GN4 will verify they are correctly signed and prevent a malicious actor from faking a GN4 authentication token.

To set this up:

1. Generate an RSA private and public key
2. Create a JWK Set containing the public key
3. Configure GN5
4. Configure GN4

### Generate an RSA private and public key

```
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate_pub.crt
```

This will generate:

1. `private.key` - use this in GN5. Put this where it is accessible to GN5. This **must** be secured since the private key is sensitive.
2. `certificate_pub.crt` - use this in the next step for GN4. Put this where it is accessible to GN4.

### Create a JWK Set containing the public key

The standard way of handling JWT signature validation is to use a JWK Set. This is fairly easy to do.

1. Use [https://jwkset.com/generate](https://jwkset.com/generate) to convert your `certificate_pub.crt` to a JWK.<br>
a. copy the text from `certificate_pub.crt` into the first section - "`PEM encoded key or certificate`" <br>
b. choose a `Key ID` (you'll need this, below)<br>
c. `Key Algorithm` is "RSA256"<br>
d. `Key Use` is "Signature"<br>
e. Press "Generate"
2. Create a file using the output JWT in the following format:
```
{
"keys": [
... TEXT FROM ABOVE ...
]
}
```
3. Save the file and make it accessible by GN4

NOTE: There are other online/offline tools that do this.

### Configure GN5

In `application.yml`:

```
cloud:
gateway:
mvc:
routes:
...
filters:
- addSimpleJwtGn4SecurityHeader=gn5.to.gn4.trusted.json.auth,file:///Users/db/delme/key/private.key,mykeyid
```

The parameters to the `addSimpleJwtGn4SecurityHeader` filter are: <br>
a) name of the header to use (must be the same as the GN4 configuration)<br>
b) URL to the private key (`private.key` generated above)<br>
c) Name of the `Key ID` (from above)

### Configure GN4

Sent environment variables like this;

```
JWTHEADERS_ValidateTokenAudienceClaimValue=g4.from.g5.proxy
JWTHEADERS_ValidateTokenAudience=true
JWTHEADERS_ValidateTokenAudienceClaimName=aud
JWTHEADERS_ValidateTokenExpiry=true
JWTHEADERS_ValidateToken=true
JWTHEADERS_ValidateTokenSignatureURL=file:///Users/db/delme/jws.json
JWTHEADERS_UpdateProfile=false
JWTHEADERS_UpdateGroup=false
JWTHEADERS_RolesHeaderName=gn5.to.gn4.trusted.json.auth
JWTHEADERS_UserNameFormat=JWT
JWTHEADERS_JwtHeaderRoleSource=DB
JWTHEADERS_UserNameJsonPath=username
JWTHEADERS_ValidateTokenAgainstURL=false
JWTHEADERS_UserNameHeaderName=gn5.to.gn4.trusted.json.auth
```

#### Audience Validation

This audience is put into the JWT by GN5. It's optional (but recommended) to validate it.

`JWTHEADERS_ValidateTokenAudienceClaimValue=g4.from.g5.proxy`
`JWTHEADERS_ValidateTokenAudience=true`
`JWTHEADERS_ValidateTokenAudienceClaimName=aud`

#### Token Expiry

Ensure the token hasn't expired yet.

`JWTHEADERS_ValidateTokenExpiry=true`

#### Validate Signature

This should be URL to the JWK Set you created, above.
NOTE: see below if you are using a file instead of a https location

`JWTHEADERS_ValidateTokenSignatureURL=file:///Users/db/delme/jws.json`

#### Other configuration

This is the meaning of the other configuration options:

`JWTHEADERS_UserNameFormat=JWT` -- expect indentity information to be in a JWT

`JWTHEADERS_UserNameJsonPath=username` -- json path (inside the JWT payload) of the username
`JWTHEADERS_ValidateTokenAgainstURL=false` -- do not validate the token against the IDP
`JWTHEADERS_UserNameHeaderName=gn5.to.gn4.trusted.json.auth`- name of the request header containing the JWT. Must be the same as the GN5 configuration.

`JWTHEADERS_UpdateProfile=false` - manage user permissions and groups in the DB
`JWTHEADERS_UpdateGroup=false`- manage user permissions and groups in the DB
`JWTHEADERS_JwtHeaderRoleSource=DB` - manage user permissions and groups in the DB

#### JWK Set in File

The older implementation of JWT Headers isn't compatible with file-base URLs for the JWKSet. This is fixed, but it will take a while for this to become "official" through the build chains.

Use `python` to serve your JWK Set file;

1. make a directory and put the JWK Set json file in it
2. run:
`python3 -m http.server 9000`
3. Use "http://localhost:9000/jwksets.json" as the JWK Set url

See the python documentation for security related to doing this.
6 changes: 6 additions & 0 deletions docs/manual/docs/GN4-Integration/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# GeoNetwork 5 Integration with GeoNetwork 4 {#toc}

There are two main ways handle authentication between GN5 and GN4.

1. [Simple Authentication](auth-json.md)
1. [JWT-Based Authentication](auth-jwt.md)
17 changes: 17 additions & 0 deletions docs/manual/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
hide:
- navigation
---

# GeoNetwork 5 {#toc}

Welcome to GeoNetwork 5.

<div class="grid cards" markdown>

- :fontawesome-solid-signs-post: [Integration with GN4](GN4-Integration/index.md)

---

Authentication passing between GN5 and GN5 via the proxy gateway.
</div>
149 changes: 149 additions & 0 deletions docs/manual/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Project information
site_name: GeoNetwork Opensource Microservices
site_description: GeoNetwork Opensource Microservices.
site_dir: target/html
site_url: https://docs.geonetwork-opensource.org/

# Repository
repo_name: geonetwork
repo_url: https://github.com/geonetwork/geonetwork
edit_uri: edit/main/docs/manual/docs

# Copyright
copyright: Copyright &copy; 2024 FAO-UN and others

extra_css:
- assets/stylesheets/extra.css

# Configuration
theme:
name: material
language: en
custom_dir: overrides
logo: assets/images/geonetwork-logo.svg
favicon: assets/images/geonetwork-logo.png
icon:
repo: fontawesome/brands/github
palette:
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: blue
toggle:
icon: material/weather-night
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/weather-sunny
name: Switch to light mode
features:
- toc.follow
- navigation.tracking
- navigation.top
- navigation.tabs
- navigation.prune
- navigation.indexes
- navigation.footer
- header.autohide
- content.tabs.link
- content.code.copy
- content.action.view
- content.action.edit
- announce.dismiss

# Plugins - install using: pip3 install -r requirements.txt
plugins:
- exclude:
glob:
- annexes/gallery/bin/README.md
- i18n:
docs_structure: suffix
reconfigure_material: true
languages:
- locale: en
name: English
build: true
default: true
site_name: 'GeoNetwork 5'
- locale: fr
name: Français
build: !ENV [FRENCH,true]
site_name: 'GeoNetwork 5'
site_description: Catalogue GeoNetwork pour répertorier, rechercher et examiner les enregistrements.
nav_translations:
Home: Home
Search: Search
Record: Record
Map: Map
- search

# Customizations
extra:
version:
provider: mike
default: stable
alias: true
homepage: https://geonetwork-opensource.org/
social:
- icon: fontawesome/brands/github
link: https://github.com/geonetwork
- icon: fontawesome/brands/docker
link: https://hub.docker.com/_/geonetwork
- icon: geonetwork/logo_bw
link: https://geonetwork-opensource.org/
name: GeoNetwork Website

# For use with --strict to produce failures on build warnings
validation:
nav:
omitted_files: warn
not_found: warn
absolute_links: warn
links:
not_found: warn
absolute_links: warn
unrecognized_links: warn

# Extensions
# - These are carefully chosen to work with pandoc markdown support for whole document translation
markdown_extensions:
- admonition
- attr_list
- def_list
- pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
options:
custom_icons:
- overrides/.icons
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.smartsymbols
- pymdownx.snippets
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
- tables
- md_in_html

# Page tree
nav:
- 'GeoNetwork 5':
- index.md
- 'GN5 to GN4 Proxy Gateway':
- GN4-Integration/index.md
- GN4-Integration/auth-json.md
- GN4-Integration/auth-jwt.md


Loading

0 comments on commit e350bcb

Please sign in to comment.