-
Notifications
You must be signed in to change notification settings - Fork 18
feat: add haproxy_spoe_auth interface library #252
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
Closed
Thanhphan1147
wants to merge
6
commits into
canonical:main
from
Thanhphan1147:feat/add-spoe-auth-lib
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
b284b83
Add haproxy_spoe_auth interface library
Thanhphan1147 350ddd0
add platform engineering as codeowner for haproxy_spoe_auth
Thanhphan1147 eba46c8
Add tests for regex, update lib docstring, address comments from orig…
Thanhphan1147 1ea1a3a
Add interface documentations
Thanhphan1147 4b9bd0f
Merge branch 'main' into feat/add-spoe-auth-lib
Thanhphan1147 d0b2b27
update relation interface
Thanhphan1147 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # charmlibs.interfaces.haproxy_spoe_auth | ||
|
|
||
| The `haproxy_spoe_auth` interface library. | ||
|
|
||
| To install, add `charmlibs-interfaces-haproxy-spoe-auth` to your Python dependencies. Then in your Python code, import as: | ||
|
|
||
| ```py | ||
| from charmlibs.interfaces import haproxy_spoe_auth | ||
| ``` | ||
|
|
||
| See the [reference documentation](https://documentation.ubuntu.com/charmlibs/reference/charmlibs/interfaces/haproxy_spoe_auth) for more. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # `spoe-auth/v0` | ||
|
|
||
| ## Usage | ||
|
|
||
| This relation interface describes the expected behavior of any charm that can work with the haproxy charm to provide authentication capabilities through SPOE ( Stream Process Offloading Engine ). | ||
|
|
||
| ## Direction | ||
|
|
||
| SPOE allows haproxy to be extended with middlewares. SPOA are agents that talk to haproxy using the Stream Process Offloading Protocol ( SPOP ). | ||
|
|
||
| Providers are agent charms that validates incoming requests, communicates to Haproxy the full redirect URL to the IDP in case of unauthenticated requests, receives the OIDC callback and finally issues a redirect to the original destination to set the authentication cookie on the client browser if the request is authenticated. | ||
|
|
||
| The haproxy-operator charm is the only requirer charm as of now. | ||
|
|
||
| ## Behavior | ||
| ### Provider | ||
|
|
||
| - Is expected to expose a TCP port receiving SPOE messages ( through SPOP ). This port needs to be communicated to the requirer ( haproxy ). | ||
| - Is expected to reply to the SPOE messages with the appropriate set-var Actions, mainly idicating whether the request is authenticated | ||
| and if not, what's the IDP redirect URL that haproxy need to use as the response. | ||
| - Is expected to expose an HTTP port receiving the OIDC callback requests. The hostname and path prefix used to route requests to | ||
| this port needs to be communicated to the requirer ( haproxy ) | ||
|
|
||
| ### Requirer ( haproxy ) | ||
|
|
||
| - Is expected to use the information available in the relation data to perform the corresponding actions. Specifically: | ||
| - Update the haproxy configuration to define SPOE message parameters, define the SPOP/redirect/callback backends and add routing rules accordingly | ||
|
|
||
|
|
||
| ## Relation Data | ||
|
|
||
| ### Provider | ||
|
|
||
| The provider exposes via its application databag informations about the SPOP and the OIDC callback endpoints via the `spop_port`, and `oidc_callback_*` attributes respectively. The provider also communicates the name of the variables for important flags such as "Is the user authenticated" (`var_authenticated`) or "The full URL to issue a redirect to the IDP" (`var_redirect_url`). The provider also exposes the name of the SPOE message, the event that should trigger the SPOE message and the name of the cookie to include in the SPOE message via the `message_name`, `event` and `cookie_name` attribute respectively. | ||
|
|
||
|
|
||
| #### Example | ||
| ```yaml | ||
| unit_data: | ||
| unit/0: | ||
| address: 10.0.0.1 | ||
|
|
||
| application_data: | ||
| spop_port: 12345 | ||
dimaqq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| event: on-frontend-http-request | ||
dimaqq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| message_name: try-auth-oidc | ||
| var_authenticated: sess.auth.is_authenticated | ||
| var_redirect_url: sess.auth.redirect_url | ||
| cookie_name: sessioncookie | ||
| oidc_callback_port: 5000 | ||
| oidc_callback_path: /oauth2/callback | ||
| hostname: auth.haproxy.internal | ||
| ``` | ||
|
|
||
| ### Requirer | ||
| No data is communicated from the requirer side. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| providers: | ||
| - name: haproxy-spoe-auth | ||
| url: https://www.github.com/canonical/haproxy-operator/haproxy_spoe_auth_operator | ||
|
|
||
| requirers: | ||
| - name: haproxy | ||
| url: https://www.github.com/canonical/haproxy-operator |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||
| [project] | ||||
| name = "charmlibs-interfaces-haproxy-spoe-auth" | ||||
| description = "The charmlibs.interfaces.haproxy_spoe_auth package." | ||||
| readme = "README.md" | ||||
| requires-python = ">=3.12" | ||||
| authors = [ | ||||
| {name="The Platform Engineering team at Canonical"}, | ||||
| ] | ||||
| classifiers = [ | ||||
| "Programming Language :: Python :: 3", | ||||
| "License :: OSI Approved :: Apache Software License", | ||||
| "Intended Audience :: Developers", | ||||
| "Operating System :: POSIX :: Linux", | ||||
| "Development Status :: 5 - Production/Stable", | ||||
| ] | ||||
| dynamic = ["version"] | ||||
| dependencies = [ | ||||
| "ops>=3.3.1", | ||||
| # "ops", | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since ops with a version specifier is required above. |
||||
| "pydantic>=2.12.4", | ||||
| ] | ||||
|
|
||||
| [dependency-groups] | ||||
| lint = [ # installed for `just lint interfaces/haproxy_spoe_auth` (unit, functional, and integration are also installed) | ||||
| # "typing_extensions", | ||||
| ] | ||||
| unit = [ # installed for `just unit interfaces/haproxy_spoe_auth` | ||||
| "ops[testing]", | ||||
| ] | ||||
| functional = [ # installed for `just functional interfaces/haproxy_spoe_auth` | ||||
| ] | ||||
| integration = [ # installed for `just integration interfaces/haproxy_spoe_auth` | ||||
| "jubilant", | ||||
| ] | ||||
|
|
||||
| [project.urls] | ||||
| "Repository" = "https://github.com/canonical/charmlibs" | ||||
| "Issues" = "https://github.com/canonical/charmlibs/issues" | ||||
|
|
||||
| [build-system] | ||||
| requires = ["hatchling"] | ||||
| build-backend = "hatchling.build" | ||||
|
|
||||
| [tool.hatch.build.targets.wheel] | ||||
| packages = ["src/charmlibs"] | ||||
|
|
||||
| [tool.hatch.version] | ||||
| path = "src/charmlibs/interfaces/haproxy_spoe_auth/_version.py" | ||||
|
|
||||
| [tool.ruff] | ||||
| extend = "../../pyproject.toml" | ||||
| src = ["src", "tests/unit", "tests/functional", "tests/integration"] # correctly sort local imports in tests | ||||
|
|
||||
| [tool.ruff.lint.extend-per-file-ignores] | ||||
| # add additional per-file-ignores here to avoid overriding repo-level config | ||||
| "tests/**/*" = [ | ||||
| # "E501", # line too long | ||||
| ] | ||||
|
|
||||
| [tool.pyright] | ||||
| extends = "../../pyproject.toml" | ||||
| include = ["src", "tests"] | ||||
| pythonVersion = "3.12" # check no python > 3.12 features are used | ||||
|
|
||||
| [tool.charmlibs.functional] | ||||
| ubuntu = [] # ubuntu versions to run functional tests with, e.g. "24.04" (defaults to just "latest") | ||||
| pebble = [] # pebble versions to run functional tests with, e.g. "v1.0.0", "master" (defaults to no pebble versions) | ||||
| sudo = false # whether to run functional tests with sudo (defaults to false) | ||||
|
|
||||
| [tool.charmlibs.integration] | ||||
| # tags to run integration tests with (defaults to running once with no tag, i.e. tags = ['']) | ||||
| # Available in CI in tests/integration/pack.sh and integration tests as CHARMLIBS_TAG | ||||
| tags = [] # Not used by the pack.sh and integration tests generated by the template | ||||
41 changes: 41 additions & 0 deletions
41
interfaces/haproxy_spoe_auth/src/charmlibs/interfaces/haproxy_spoe_auth/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Copyright 2025 Canonical Ltd. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """The charmlibs.interfaces.haproxy_spoe_auth package.""" | ||
|
|
||
| from ._spoe_auth import ( | ||
| HaproxyEvent, | ||
| SpoeAuthAvailableEvent, | ||
| SpoeAuthInvalidRelationDataError, | ||
| SpoeAuthProvider, | ||
| SpoeAuthProviderAppData, | ||
| SpoeAuthProviderUnitData, | ||
| SpoeAuthRemovedEvent, | ||
| SpoeAuthRequirer, | ||
| ) | ||
| from ._version import __version__ as __version__ | ||
|
|
||
| # only the names listed in __all__ are imported when executing: | ||
| # from charmlibs.haproxy_spoe_auth import * | ||
| __all__ = [ | ||
| 'HaproxyEvent', | ||
| 'SpoeAuthAvailableEvent', | ||
| 'SpoeAuthInvalidRelationDataError', | ||
| 'SpoeAuthProvider', | ||
| 'SpoeAuthProvider', | ||
| 'SpoeAuthProviderAppData', | ||
| 'SpoeAuthProviderUnitData', | ||
| 'SpoeAuthRemovedEvent', | ||
| 'SpoeAuthRequirer', | ||
| ] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: what happens when the provider is scaled out?
Are all ip addresses equivalent? Should haproxy use a random ip in the set? Can the provider unit signal that while it exists, it's not ready to receive spop callbacks?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible for haproxy to handle multiple units of the spoe-agent. In short, haproxy will use the addresses for 2 backends:
If there are 2 units with IP at
10.0.0.1and10.0.0.2then the section will look something like this:As we'll add health-check for each of the server in the backend, if one of the provider's unit is not responding to health-check requests, then the unit will be marked as down and request won't be forwarded to it anymore. If no backend is available I believe haproxy will give you a 502/503 directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing to note also that haproxy makes no assumption about the implementation details of the provider, so it's entirely possible that the agent is not stateless and doesn't work well when scaled out, but in those cases the agent charm should be conscious about that and not allow more than one unit.
And haproxy will also try its best to ensure stickiness with
but ultimately ensuring consistency when scaled out should be the responsibility of the agent charm's workload I think