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

Feat: JSON output in biscuit inspect #48

Merged
merged 5 commits into from
Sep 19, 2023
Merged

Feat: JSON output in biscuit inspect #48

merged 5 commits into from
Sep 19, 2023

Conversation

divarvel
Copy link
Collaborator

@divarvel divarvel commented Jul 20, 2023

The --json flag on biscuit inspect now provides results in a structured JSON format:

{
  "token": {
    "sealed": false,
    "root_key_id": null,
    "blocks": [
      {
        "code": "user(\"1234\");\n",
        "external_key": null,
        "revocation_id": "0ca759b390fbbb20461d9a2ca0b335371020262cbefc50854ee5cdc000339a877788f40baa0eb4af005eab0f26e556c0a86f063618d720c0619d7a67320de707"
      },
      {
        "code": "user(\"4567\");\n",
        "external_key": null,
        "revocation_id": "c7e85b0c3bad81a1f381985042df1bd6b04f5c0029b0c38ec8565d767474953e9087af66ac396dd246f416038b7c02d2e943a1c5a5a4f282b7b3f4fcbc19fb0d"
      }
    ]
  },
  "signatures_check": true,
  "auth": {
    "policies": [
      "allow if user($u)"
    ],
    "result": [
      0,
      "allow if user($u)"
    ]
  },
  "query": {
    "query": "user($u) <- user($u)",
    "query_all": true,
    "facts": [
      "user(\"1234\")",
      "user(\"4567\")"
    ]
  }
}

If either the signature, the authorization, or the query fail during execution, a JSON value is still sent to stdout, with structured errors, an error message is sent to stderr, and the command exits with a non-zero error code.

Evaluation still aborts for input errors (missing files, invalid datalog input for the authorizer, etc). In that case, a JSON object containing an error field is sent to stdout, an error message is sent to stderr, and the command exits with a non-zero error code.

This change required extensive modifications: the implementation interleaved console output with computations. This meant that even if an error was raised and execution aborted, text output would still be shown. For JSON output, this doesn't work, as all results need to be collected before being displayed.

The inspection function now returns an InspectionResults value, that can either be rendered as text, or as JSON. To be able to generate this value, even in the case of authorization errors, the execution logic had to be updated to not abort early in case of:

  • failed signature checks
  • failed authorization
  • failed queries

@divarvel divarvel changed the title Json Feat: JSON output in biscuit inspect Jul 20, 2023
Base automatically changed from authorize-snapshot to main July 21, 2023 13:54
src/cli.rs Show resolved Hide resolved
src/inspect.rs Show resolved Hide resolved
Token::FailedLogic(l) => display_logic_error(&policies, &l),
Token::RunLimit(l) => display_run_limit(&l),
_ => {}
if let Ok(biscuit) = sig_result {

Choose a reason for hiding this comment

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

Personal opinion, but wouldn't it be easier to read with a match block ?

Copy link
Collaborator Author

@divarvel divarvel Jul 25, 2023

Choose a reason for hiding this comment

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

for huge blocks like this, i like if-let, as it indents things less. I also think the if/else semantics maps better to what we are actually doing, especially since we're performing side effects.

src/inspect.rs Show resolved Hide resolved
src/inspect.rs Show resolved Hide resolved
@divarvel
Copy link
Collaborator Author

divarvel commented Jul 27, 2023

Common behaviour

exit code is never 0 if anything went wrong.

stdout always contains a valid JSON object even for stop-the-world errors

the json output is either {"error": <something> }
or {"token": TokenDescription, "signatures_check": boolean | null, "auth": AuthResult | null, "query": QueryResult | null}

stderr only contains something if the exit code is not 0, it contains a text representation of the error.

Open questions

  • How to represent errors in auth and query ?
  • should we always try to at least provide token if we can?

Good case

no input errors, valid biscuit, correct signatures, successful auth & queries

exit code: 0

standard error: nothing

standard output:

{
  "token": {
    "sealed": false,
    "root_key_id": null,
    "blocks": [
      {
        "code": "user(\"1234\");\n",
        "external_key": null,
        "revocation_id": "0ca759b390fbbb20461d9a2ca0b335371020262cbefc50854ee5cdc000339a877788f40baa0eb4af005eab0f26e556c0a86f063618d720c0619d7a67320de707"
      },
      {
        "code": "user(\"4567\");\n",
        "external_key": null,
        "revocation_id": "c7e85b0c3bad81a1f381985042df1bd6b04f5c0029b0c38ec8565d767474953e9087af66ac396dd246f416038b7c02d2e943a1c5a5a4f282b7b3f4fcbc19fb0d"
      }
    ]
  },
  "signatures_check": true,
  "auth": {
    "policies": [
      "allow if user($u)"
    ],
    "result": [
      0,
      "allow if user($u)"
    ]
  },
  "query": {
    "query": "user($u) <- user($u)",
    "query_all": true,
    "facts": [
      "user(\"1234\")",
      "user(\"4567\")"
    ]
  }
}

Failed authorization (authorizer datalog correctly parsed)

{
  "token": {
    "sealed": false,
    "root_key_id": null,
    "blocks": [
      {
        "code": "user(\"1234\");\n",
        "external_key": null,
        "revocation_id": "0ca759b390fbbb20461d9a2ca0b335371020262cbefc50854ee5cdc000339a877788f40baa0eb4af005eab0f26e556c0a86f063618d720c0619d7a67320de707"
      }
    ]
  },
  "signatures_check": true,
  "auth": {
    "policies": [
      "deny if true"
    ],
    "result": {
      "error": {
        "FailedLogic": {
          "Unauthorized": {
            "policy": {
              "Deny": 0
            },
            "checks": []
          }
        }
      }
    }
  },
  "query": null
}

Input error before biscuit parsing (inconsistent args, incorrect biscuit file name, etc)

exit code: 1

stderr: Error: I cannot read input from both stdin and an interactive editor. Please use proper files or flags instead.

stdout:

{"error":"I cannot read input from both stdin and an interactive editor. Please use proper files or flags instead."}

Input error after biscuit parsing (incorrect datalog for the authorizer, missing public key when an authorizer is specified)

exit code: 1

stderr: Error: A public key is required when authorizing a biscuit

stdout:

{"error":"A public key is required when authorizing a biscuit"}

alternatives:

if the biscuit token can be read, shouldn't we always return it in stdout, and return the error in the auth field of the json?

pros

we always return as much information as we can

cons

code is more complex. here it is clearly a user error, so failing early is okay imo

@divarvel divarvel mentioned this pull request Nov 1, 2021
@divarvel divarvel marked this pull request as ready for review August 9, 2023 08:18
The `--json` flag on `biscuit inspect` now provides results in a structured JSON format:

```json
{
  "token": {
    "sealed": false,
    "root_key_id": null,
    "blocks": [
      {
        "code": "user(\"1234\");\n",
        "external_key": null,
        "revocation_id": "0ca759b390fbbb20461d9a2ca0b335371020262cbefc50854ee5cdc000339a877788f40baa0eb4af005eab0f26e556c0a86f063618d720c0619d7a67320de707"
      },
      {
        "code": "user(\"4567\");\n",
        "external_key": null,
        "revocation_id": "c7e85b0c3bad81a1f381985042df1bd6b04f5c0029b0c38ec8565d767474953e9087af66ac396dd246f416038b7c02d2e943a1c5a5a4f282b7b3f4fcbc19fb0d"
      }
    ]
  },
  "signatures_check": true,
  "auth": {
    "policies": [
      "allow if user($u)"
    ],
    "result": {
      "Ok": [
        0,
        "allow if user($u)"
      ]
    }
  },
  "query": {
    "query": "user($u) <- user($u)",
    "query_all": true,
    "facts": [
      "user(\"1234\")",
      "user(\"4567\")"
    ]
  }
}
```

If either the signature, the authorization, or the query fail during execution, a JSON value is still sent to stdout, with structured errors, an error message is sent to stderr, and the command exits with a non-zero error code.

Evaluation still aborts for input errors (missing files, invalid datalog input for the authorizer, etc). In that case, nothing is sent to stdout, an error message is sent to stderr, and the command exits with a non-zero error code.

This change required extensive modifications: the implementation interleaved console output with computations. This meant that even if an error was raised and execution aborted, text output would still be shown. For JSON output, this doesn't work, as all results need to be collected before being displayed.

The inspection function now returns an `InspectionResults` value, that can either be rendered as text, or as JSON. To be able to generate this value, even in the case of authorization errors, the execution logic had to be updated to not abort early in case of:

- failed signature checks
- failed authorization
- failed queries
The naïve encoding of `Result` in JSON is an object with either a `Ok`
or a `Err` field, containing the JSON encoding of either the value or the error. This is a rather uncommon JSON encoding for errors and ties the output to the implementation language.

This commit introduces an equivalent enum, with a different encoding:

- in case of success, the value is serialized without any wrapping
- in case of error, the error is wrapped in an object with a single `error` field

```json
{
    "query": "user($u) <- user($u)",
    "query_all": false,
    "facts": [ "user(\"1234\")"]
}
```

```json
{
    "query": "user($u) <- user($u)",
    "query_all": false,
    "facts": {
      "error": …
    }
}
```
@divarvel divarvel merged commit 9d7b015 into main Sep 19, 2023
1 check passed
@divarvel divarvel deleted the json branch September 19, 2023 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants