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

OpenFGA authorization driver #12252

Merged
merged 28 commits into from
Oct 27, 2023
Merged

OpenFGA authorization driver #12252

merged 28 commits into from
Oct 27, 2023

Conversation

markylaing
Copy link
Contributor

@markylaing markylaing commented Sep 14, 2023

This PR adds an OpenFGA authentication driver to LXD https://warthogs.atlassian.net/browse/LXD-389?atlOrigin=eyJpIjoiZWExM2NhNjgwMWU2NDZhMGJhODc5MjAzYmZiZGFkNTYiLCJwIjoiaiJ9.

The spec can be found at https://discourse.ubuntu.com/t/openfga-authorization-driver/38979

Work still to be done:

  • Setting authentication model ID in database. Avoid race condition.
  • Permission sync remove dangling permissions.
  • Remove all usages of context.Background().
  • Unit and system tests (also system tests for RBAC).
  • Documentation

@markylaing markylaing added the Feature New feature, not a bug label Sep 14, 2023
@markylaing markylaing self-assigned this Sep 14, 2023
@github-actions github-actions bot added the Documentation Documentation needs updating label Sep 14, 2023
@markylaing markylaing force-pushed the openfga branch 5 times, most recently from 29aea6c to fdd4e9b Compare September 15, 2023 00:17
@tomponline
Copy link
Member

@markylaing i know this is heavily under development for now, so no need to do this right away, but when it comes to review and merge, I would appreciate it if you could split this into two PRs; 1 for the interface and hook changes across the code base, and 2 introducing the new openfga authorization driver.

@markylaing
Copy link
Contributor Author

RBAC tests passing also :)

Copy link
Contributor Author

@markylaing markylaing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomponline I've added a few comments to start discussion on a few of the main things I need feedback on. There are still a fair few things to do before this is ready for a proper review.

lxd/auth/driver_openfga_model.openfga Show resolved Hide resolved
lxd/auth/driver_openfga_model.openfga Show resolved Hide resolved

var builtinAuthorizationModel client.ClientWriteAuthorizationModelRequest

err = json.Unmarshal([]byte(authModel), &builtinAuthorizationModel)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrations

Here authModel is the json string that is generated via make update-openfga. If there is an existing model in the store, we ensure that this model is exactly equal to the model we have statically defined. This is where we would have an opportunity to detect different model versions and apply update logic.

Migrations in OpenFGA follow a pretty basic idea. The idea is to create an intermediary model which supports both the new and the old, then write all the tuples required to support the new schema, delete all the tuples that are no longer required, and finally write the updated model (see https://openfga.dev/docs/modeling/migrating/migrating-relations).

In practice, I think we will likely have something like:

  • Under an lxd/auth/openfga directory:
    • model_v1.openfga
    • model_v1.go
    • model_v2_compatible.openfga
    • model_v2_compatible.go
    • model_v2.openfga
    • model_v2.go
    • migrations.go
    • synchronise.go
  • The migrations.go file will contain a map of methods which accept an openfga client and performs the necessary actions to migrate from one schema to the next. These methods will only be valid while the compatible model is in place.
  • The synchronise.go file will contain a map of methods which accept an fga client and a set of LXD resources and ensure they match 1:1 with what is present in the OpenFGA store. Like migrations, this is also model specific.
  • When we connect to the OpenFGA server:
    1. Get the current auth model.
    2. Check if it is the latest version. If it is, sync resources and continue. Otherwise....
    3. Write the compatible model.
    4. Call the associated method from migrations.go.
    5. Write the latest model.
    6. Perform a final resource sync from synchronise.go.

Hopefully we won't have to do this very often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can handle this when we come to update the model.

This makes it available from other network namespaces when running
clustering tests.

Signed-off-by: Mark Laing <[email protected]>
@markylaing
Copy link
Contributor Author

I have added reference and explanation documentation. I think this will also need a how-to but I need to figure out how to easily set up an OIDC server in order to do that (keycloak requires a lot of clicking around and openjdk-17).

TLS authorization and RBAC have their own sections in the authorization
page now so these are removed. Additionally I have updated the OIDC and
Candid sections to notify users that they must configure authorization,
else their servers are not secure.

Signed-off-by: Mark Laing <[email protected]>
Copy link
Member

@tomponline tomponline left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is great work.
Please can you address/discuss the changes via your existing github card for further minor work.

But I'll merge this now as I can't see any major issues.

Thanks for your thoroughness.

"github.com/canonical/lxd/shared/logger"
)

func WriteOpenFGAAuthorizationModel(ctx context.Context, apiURL string, apiToken string, storeID string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a docblock to this explaining what it does please.

}

if readModelResponse.AuthorizationModel.SchemaVersion != builtinAuthorizationModel.SchemaVersion {
return fmt.Errorf("Existing OpenFGA model has schema version %q, but our model has version %q", readModelResponse.AuthorizationModel.SchemaVersion, builtinAuthorizationModel.SchemaVersion)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Des this stop LXD from starting or just log a warning? Does it prevent the authorizer from working?

@@ -1802,6 +1803,246 @@ func (d *Daemon) Stop(ctx context.Context, sig os.Signal) error {
return err
}

// Setup OpenFGA.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docblock comment explaining what this function does would be nice.

if openFGAAuthorizationModelID == "" {
// We should never be missing the model ID at start up if we have other connection details (this means
// something went wrong the last time we tried to set it up).
logger.Warn("OpenFGA authorization driver is misconfigured, skipping...")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this will fallback to TLS only mode or only allow unix socket operation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either both should be warnings or both should be errors, as they both result in the authorizer not working right?

} else {
err = d.setupOpenFGA(openfgaAPIURL, openfgaAPIToken, openfgaStoreID, openFGAAuthorizationModelID)
if err != nil {
logger.Error("Failed to configure OpenFGA. Reverting to default TLS authorization", logger.Ctx{"error": err})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above

@@ -808,6 +813,50 @@ func doApi10Update(d *Daemon, r *http.Request, req api.ServerPut, patch bool) re
return response.EmptySyncResponse
}

func doApi10PreNotifyTriggers(d *Daemon, clusterChanged map[string]string, newClusterConfig *clusterConfig.Config) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docblock explaining what this is and what it does

@@ -763,6 +763,11 @@ func doApi10Update(d *Daemon, r *http.Request, req api.ServerPut, patch bool) re
}
})

err = doApi10PreNotifyTriggers(d, clusterChanged, newClusterConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to support a revert here if something fails later on?

@tomponline tomponline merged commit db2afaa into canonical:main Oct 27, 2023
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation Documentation needs updating Feature New feature, not a bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants