Skip to content

Commit

Permalink
Fb/alternative uaa user from env (#73)
Browse files Browse the repository at this point in the history
* new generic env variable replacer

* some more reordering

* increase mask length

* docs: uaa: basic syntax for token via env calls

* docs: uaa: added general motivation for env replacement

* test: basic testing for cli env replacement

* test: cli testing for too many or too few arguments
  • Loading branch information
rlindner81 authored Apr 26, 2024
1 parent f8ffc4d commit 6e8825b
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 22 deletions.
49 changes: 32 additions & 17 deletions docs/user-authentication/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ Commands for this area are:
~ are read-only commands
```

It can be beneficial to pass _credentials_, especially long-term credentials like a password, along through environment
variables instead of commandline arguments. This is, for example, the default approach in pipeline solutions like
Jenkins and Github Actions. To enable this, you can replace the following arguments with their corresponding
environment variable for all `uaa` commands.

| argument | environment variable |
| :--------- | :------------------- |
| `USERNAME` | `UAA_USERNAME` |
| `PASSWORD` | `UAA_PASSWORD` |
| `PASSCODE` | `UAA_PASSCODE` |

## Decoding JWTs

Without even setting up MTX Tool, you can always take any web token you have in your clipboard and run `mtx uaad <jwt>`.
Expand All @@ -66,10 +77,10 @@ privileges that particular user has. In order to achieve this:
If your server exposes an endpoint with JWT authentication, which is the default in CAP, then you can
access these endpoint with a generic client JWT that mtx can get for you.

| purpose | command |
| :------------------------------------------------ | :---------------------------------- |
| obtain client JWT of provider subaccount (paas) | `mtx uaac` |
| obtain client JWT of subscriber subaccount (saas) | `mtx uaac <subdomain or tenant id>` |
| purpose | command |
| :----------------------------------------- | :---------------------------------- |
| client JWT of provider subaccount (paas) | `mtx uaac` |
| client JWT of subscriber subaccount (saas) | `mtx uaac <subdomain or tenant id>` |

Set the `Authorization` header as `Bearer <jwt>` in HTTP requests for the endpoints to pass validation. We use
[curl](https://curl.se) for this, but any other HTTP client works as well.
Expand All @@ -86,10 +97,11 @@ then they can obtain their passcode by logging in at
Using this one-time passcode, you can either get a regular JWT for accesses _as that user_, or their extended user
info:

| purpose | command |
| :-------------------------- | :-------------------------------------------------------- |
| obtain user JWT | `mtx uaap <passcode> <subdomain or tenant id>` |
| obtain user JWT + user info | `mtx uaap <passcode> <subdomain or tenant id> --userinfo` |
| purpose | command |
| :-------------------- | :---------------------------------------------------------- |
| user JWT | `mtx uaap <passcode> <subdomain or tenant id>` |
| user JWT via&nbsp;env | `UAA_PASSCODE=<passcode> mtx uaap <subdomain or tenant id>` |
| user JWT + user info | `mtx uaap <passcode> <subdomain or tenant id> --userinfo` |

Like before, if the command is run without the `subdomain or tenant_id` parameter, the tool will assume you mean the
provider subaccount (paas).
Expand All @@ -110,21 +122,24 @@ information.
Similarly to using a one-time passcode, you can access APIs as some user if you have both the username and password of
that user.

| purpose | command |
| :-------------------------- | :------------------------------------------------------------------- |
| obtain user JWT | `mtx uaau <username> <password> <subdomain or tenant id>` |
| obtain user JWT + user info | `mtx uaau <username> <password> <subdomain or tenant id> --userinfo` |
| purpose | command |
| :-------------------- | :---------------------------------------------------------------------------------- |
| user JWT | `mtx uaau <username> <password> <subdomain or tenant id>` |
| user JWT via&nbsp;env | `UAA_USERNAME=<username> UAA_PASSWORD=<password> mtx uaau <subdomain or tenant id>` |
| user JWT + user info | `mtx uaau <username> <password> <subdomain or tenant id> --userinfo` |

## Accessing Service APIs

You can access services in the same way that the server accesses them, usually for debugging purposes. You will
need to know the label of the service and it needs to be bound to the app which is configured for user authentication.

| purpose | command |
| :---------------------------- | :------------------------------------------------------------------- |
| obtain client JWT for service | `mtx uaasc <service> <subdomain or tenant id>` |
| obtain user JWT for service | `mtx uaasp <service> <passcode> <subdomain or tenant id>` |
| obtain user JWT for service | `mtx uaasu <service> <username> <password> <subdomain or tenant id>` |
| purpose | command |
| :-------------------- | :--------------------------------------------------------------------------------------------- |
| client JWT | `mtx uaasc <service> <subdomain or tenant id>` |
| user JWT | `mtx uaasp <service> <passcode> <subdomain or tenant id>` |
| user JWT via&nbsp;env | `UAA_PASSCODE=<passcode> mtx uaasp <service> <subdomain or tenant id>` |
| user JWT | `mtx uaasu <service> <username> <password> <subdomain or tenant id>` |
| user JWT via&nbsp;env | `UAA_USERNAME=<username> UAA_PASSWORD=<password> mtx uaasu <service> <subdomain or tenant id>` |

For example `destination` is the label of the [BTP destination service](https://help.sap.com/docs/CP_CONNECTIVITY).
You will also need to choose a trusted subdomain to use, for example `skyfin-company`.
Expand Down
14 changes: 12 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,26 @@ const checkOption = async (cliOption, args) => {
if (!firstArg || !commandVariants.includes(firstArg)) {
return false;
}
const allPassArgs = [].concat(requiredPassArgs, optionalPassArgs);
const command = commandVariants[commandVariants.length - 1];
let flagValues = null;

// NOTE: this mixes in (required and optional) positional arguments that can
// be replaced by env variables.
for (const [index, passArg] of allPassArgs.entries()) {
const envVariable = PASS_ARG_META[passArg]?.envVariable;
const value = envVariable && process.env[envVariable];
if (envVariable && value) {
passArgs.splice(index, 0, value);
}
}
assert(
passArgs.length >= requiredPassArgs.length,
'command "%s" requires %s %s',
command,
requiredPassArgs.length === 1 ? "argument" : "arguments",
requiredPassArgs.join(", ")
);
const allPassArgs = [].concat(requiredPassArgs, optionalPassArgs);
assert(
passArgs.length <= allPassArgs.length,
'command "%s" takes %s %s',
Expand All @@ -59,7 +69,7 @@ const checkOption = async (cliOption, args) => {
});
flagValues = optionalFlagArgs.map((flag) => flagArgs.includes(flag));
}
const maskedPassArgs = passArgs.map((arg, index) => (PASS_ARG_META[allPassArgs[index]]?.sensitive ? "***" : arg));
const maskedPassArgs = passArgs.map((arg, index) => (PASS_ARG_META[allPassArgs[index]]?.sensitive ? "*****" : arg));
!silent && console.log("running", command, ...maskedPassArgs, ...flagArgs);
const context = passContext ? await newContext({ usePersistedCache: useCache, isReadonlyCommand: readonly }) : null;
danger && !flagArgs.includes(FORCE_FLAG) && (await _dangerGuard());
Expand Down
5 changes: 3 additions & 2 deletions src/cliOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ const PASS_ARG = Object.freeze({
});
const PASS_ARG_META = Object.freeze({
[PASS_ARG.TOKEN]: { sensitive: true },
[PASS_ARG.PASSCODE]: { sensitive: true },
[PASS_ARG.PASSWORD]: { sensitive: true },
[PASS_ARG.PASSCODE]: { sensitive: true, envVariable: "UAA_PASSCODE" },
[PASS_ARG.USERNAME]: { envVariable: "UAA_USERNAME" },
[PASS_ARG.PASSWORD]: { sensitive: true, envVariable: "UAA_PASSWORD" },
});

const FLAG_ARG = Object.freeze({
Expand Down
205 changes: 205 additions & 0 deletions test/__snapshots__/cli.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`cli tests uaa service user via env with service 1`] = `
[
[
"command_and_control",
"freddy",
"is_ready",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa service user via env with service 2`] = `
[
"running",
"--uaa-service-user",
"command_and_control",
"freddy",
"*****",
]
`;

exports[`cli tests uaa service user via env with service tenant 1`] = `
[
[
"command_and_control",
"freddy",
"is_ready",
"troll_tenant",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa service user via env with service tenant 2`] = `
[
"running",
"--uaa-service-user",
"command_and_control",
"freddy",
"*****",
"troll_tenant",
]
`;

exports[`cli tests uaa service user with service username password 1`] = `
[
[
"command_and_control",
"freddy",
"is_ready",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa service user with service username password 2`] = `
[
"running",
"--uaa-service-user",
"command_and_control",
"freddy",
"*****",
]
`;

exports[`cli tests uaa service user with service username password tenant 1`] = `
[
[
"command_and_control",
"freddy",
"is_ready",
"troll_tenant",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa service user with service username password tenant 2`] = `
[
"running",
"--uaa-service-user",
"command_and_control",
"freddy",
"*****",
"troll_tenant",
]
`;

exports[`cli tests uaa user via env 1`] = `
[
[
"freddy",
"is_ready",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa user via env 2`] = `
[
"running",
"--uaa-user",
"freddy",
"*****",
]
`;

exports[`cli tests uaa user via env with tenant 1`] = `
[
[
"freddy",
"is_ready",
"troll_tenant",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa user via env with tenant 2`] = `
[
"running",
"--uaa-user",
"freddy",
"*****",
"troll_tenant",
]
`;

exports[`cli tests uaa user with too few variables 1`] = `
[
"error: command "--uaa-user" requires arguments USERNAME, PASSWORD",
]
`;

exports[`cli tests uaa user with too many variables 1`] = `
[
"error: command "--uaa-user" takes arguments USERNAME, PASSWORD, TENANT",
]
`;

exports[`cli tests uaa user with username password 1`] = `
[
[
"freddy",
"is_ready",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa user with username password 2`] = `
[
"running",
"--uaa-user",
"freddy",
"*****",
]
`;

exports[`cli tests uaa user with username password tenant 1`] = `
[
[
"freddy",
"is_ready",
"troll_tenant",
],
[
false,
false,
],
]
`;

exports[`cli tests uaa user with username password tenant 2`] = `
[
"running",
"--uaa-user",
"freddy",
"*****",
"troll_tenant",
]
`;
Loading

0 comments on commit 6e8825b

Please sign in to comment.