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

Fix ACL requirements for job details UI #11672

Merged
merged 12 commits into from
Jan 13, 2022
Merged

Fix ACL requirements for job details UI #11672

merged 12 commits into from
Jan 13, 2022

Conversation

lgfa29
Copy link
Contributor

@lgfa29 lgfa29 commented Dec 14, 2021

#11078 added a new component to compute and display the job status in individual clients. This resulted in an unexpected increase in the ACL permissions required to navigate to the job detail page. Namely the job detail page would now require

node {
  policy = "read"
}

This resulted in unexpected Not Authorized error page (#11660).

This PR adds a new ability to check for read node permission. It also updates the job details components to only render pieces and components that require node data when ACL permits.

@lgfa29 lgfa29 added this to the 1.2.4 milestone Dec 14, 2021
@lgfa29 lgfa29 requested review from ChaiWithJai and a team December 14, 2021 01:20
@github-actions
Copy link

github-actions bot commented Dec 14, 2021

Ember Asset Size action

As of fe643d1

Files that got Bigger 🚨:

File raw gzip
nomad-ui.js +869 B +624 B

Files that stayed the same size 🤷‍:

File raw gzip
vendor.js 0 B 0 B
nomad-ui.css 0 B 0 B
vendor.css 0 B 0 B

@github-actions
Copy link

github-actions bot commented Dec 14, 2021

Ember Test Audit comparison

main fe643d1 change
passes 1203 1211 +8
failures 0 0 0
flaky 0 0 0
duration 8m 08s 038ms 9m 30s 177ms +1m 22s 139ms

@github-actions
Copy link

Ember Test Audit flaky tests

Ember Test Audit detected these flaky tests on main:

  • Acceptance | job dispatch: required meta fields are properly indicated

@lgfa29 lgfa29 marked this pull request as draft December 14, 2021 03:10
Copy link

@gregone gregone left a comment

Choose a reason for hiding this comment

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

I am not versed in all of nomad's ACL minutiae, but from a code perspective, this looks fine. Just one comment about the preloading of all nodes only for read (and not write) users?

// Optimizing future node look ups by preemptively loading all nodes if
// necessary and allowed.
if (model.get('hasClientStatus') && this.can.can('read client')) {
await this.store.findAll('node');
Copy link

Choose a reason for hiding this comment

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

Do users with write access not need this optimization for some reason?

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'm not sure if this is how it works in other projects, but in Nomad ACL system a write permission assumes that you have read as well:
https://github.com/hashicorp/nomad/pull/11672/files#diff-e4c09a2e6416b7748fae6b3d5e3010f68eb770c0b1d757eed0ab8287146bb1b2R18

Copy link
Contributor

@ChaiWithJai ChaiWithJai left a comment

Choose a reason for hiding this comment

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

This diff is exposing how tightly-coupled the jobs route hierarchy template components are. Particularly, based on how many repeat conditionals we're using across the templates and the high use of prop-drilling.

I'm concerned that we won't be able to actually catch future bugs and continue bloating our templates with conditional checks.

Let's chat regarding drawing out the behavior for ACL token checks and move the conditional checks up the job chain to clean up the repeat code in our templates.

We can accomplish this with using a contextual component pattern and delete a couple hundred lines of code in the process.

@gotoClients={{this.gotoClients}} />

<JobPage::Parts::Summary @job={{this.job}} @forceCollapsed="true" />
{{#if (can "read client")}}
Copy link
Contributor

Choose a reason for hiding this comment

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

This check can go into the JobClientStatusSummary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately it cant'...as soon as Ember accesses jobClientStatus it will make an API request to get the Node information, which will 403.

I moved the check to the param access instead, does this look better?

<JobPage::Parts::JobClientStatusSummary
  @job={{this.job}}
  @jobClientStatus={{if (can "read client") this.jobClientStatus}}
  @forceCollapsed={{cannot "read client"}}
  @gotoClients={{this.gotoClients}} />

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm... I think this breaks my mental model of Ember, and my understanding of the Ember Way.

Currently, we're loading all the nodes into the store when we enter the jobs/job route. And then, we peek the store (no API call) when we're in a nested controller. To my knowledge this isn't a data loading component either.

Can we move this conversation to Whimsical and map this out? That behavior sounds like a lurking bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, we're loading all the nodes into the store when we enter the jobs/job route.

That's the problem I'm solving: we don't want to do this if the user's ACL token doesn't have permission to access the /v1/node API endpoint.

But if we try to read a node from the store, Ember will attempt to fetch it but will receive a 403.

Not sure if this what you're looking for, but this is the flow:

image

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. We're using the computed macro jobClientStatus in the wrong place. JobClientStatusSummary is the lowest common ancestor that should compute and store this derived state.
  2. The API call happens on the route which is protected by the ember-can check. The associated controllers aren't fetching nodes (with the exception of the jobs/job/clients controller, and this tab should be protected using the can helper -- this is another associated bug for this issue) because they're peaking from the store. The component is not fetching data either. Now, there's one more concern, the computed macro jobClientStatus this should not be making any API calls, if it is then its a bug.

ui/app/templates/components/job-page/system.hbs Outdated Show resolved Hide resolved
@@ -8,7 +8,7 @@
{{/if}}
<li data-test-tab="allocations"><LinkTo @route="jobs.job.allocations" @query={{hash namespace=this.job.namespace.id}} @model={{@job}} @activeClass="is-active">Allocations</LinkTo></li>
<li data-test-tab="evaluations"><LinkTo @route="jobs.job.evaluations" @query={{hash namespace=this.job.namespace.id}} @model={{@job}} @activeClass="is-active">Evaluations</LinkTo></li>
{{#if (and this.job.hasClientStatus (not this.job.hasChildren))}}
{{#if (and (can "read client") (and this.job.hasClientStatus (not this.job.hasChildren)))}}
Copy link
Contributor

Choose a reason for hiding this comment

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

We should move the shouldDisplayClientInformation into AbstractJob and then we'll have access to this property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't seem to work for the subnav. Do components inherit property from parents?

ui/app/components/job-page/parameterized-child.js Outdated Show resolved Hide resolved
get isExpanded() {
if (this.forceCollapsed) return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets turn forceCollapsed into a reactive getter and then this no longer needs to be a computed property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if I follow.

forcedCollapsed is an external property that is set by the caller to force the collapse state. isExpanded is the getter used to set the component state.

@gregone gregone self-requested a review December 14, 2021 17:02
@lgfa29 lgfa29 self-assigned this Dec 14, 2021
@lgfa29
Copy link
Contributor Author

lgfa29 commented Dec 14, 2021

This diff is exposing how tightly-coupled the jobs route hierarchy template components are. Particularly, based on how many repeat conditionals we're using across the templates and the high use of prop-drilling.

Yeah, there's quite a bit of repetition, but also these pages are similar but not identical. We could try to DRY some of it, but personally I'm not sure it would be bring enough benefits. There's always some quirky details that differ among them.

I'm concerned that we won't be able to actually catch future bugs and continue bloating our templates with conditional checks.

The test suite uses a common module, so a lot of the shared behaviour is tested automatically across all job types.

Let's chat regarding drawing out the behavior for ACL token checks and move the conditional checks up the job chain to clean up the repeat code in our templates.

We can accomplish this with using a contextual component pattern and delete a couple hundred lines of code in the process.

Not sure if I follow this part. The ACL check conditionally renders a specific part of the page, so I think it needs to be done at the component level?

@lgfa29 lgfa29 marked this pull request as ready for review December 14, 2021 21:20
Comment on lines -33 to +55
assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)
const expectedURL = new URL(
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace),
window.location
);
const gotURL = new URL(currentURL(), window.location);

assert.deepEqual(gotURL.path, expectedURL.path);
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams);
assert.equal(document.title, `Job ${job.name} - Nomad`);
});

test('the subnav links to overview', async function(assert) {
await JobDetail.tabFor('overview').visit();
assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)

const expectedURL = new URL(
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace),
window.location
);
const gotURL = new URL(currentURL(), window.location);

assert.deepEqual(gotURL.path, expectedURL.path);
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams);
Copy link
Contributor Author

@lgfa29 lgfa29 Dec 14, 2021

Choose a reason for hiding this comment

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

I have no idea why these started to fail 🤷

The currentURL would have and extra query params for sort, so I'm parsing the URL to ignore order.

Copy link
Contributor

Choose a reason for hiding this comment

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

This fails on periodic and parameterized jobs. Likely points to some more lurking bugs in how we handle periodic and parameterized jobs.

Comment on lines +175 to +176
await Tokens.visit();
await Tokens.secret(clientToken.secretId).submit();
Copy link
Contributor Author

@lgfa29 lgfa29 Dec 14, 2021

Choose a reason for hiding this comment

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

I think that we can't set a token directly into localStorage after visiting a page? I'm not sure if I missing something, but it didn't work in a few acceptance tests where visit is called in the beforeEach hook, so I had to do it this way.

And TBH I kind of like it, feels more intentional 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to establish boundaries for API's we don't control. Ideally, we should wrap localStorage access into a service and then write mocks.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bug. We initialize query parameters on period and parameterized jobs whereas we don't follow this behavior for other jobs. Will need to take a deeper dive into this.

@github-actions
Copy link

Ember Test Audit flaky tests

Ember Test Audit detected these flaky tests on main:

  • Acceptance | task group detail: /jobs/:id/:task-group second breadcrumb should link to the job for the task group
  • Integration | Component | TopoViz: clicking on an allocation in a deeply nested TopoViz::Node will associate sibling allocations with curves
  • Integration | Component | TopoViz: when the count of sibling allocations is high enough relative to the node count, curves are not rendered
  • Integration | Utility | exec-socket-xterm-adapter: resizing the window passes a resize message through the socket

Copy link
Contributor

@ChaiWithJai ChaiWithJai left a comment

Choose a reason for hiding this comment

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

We're using the computed macro jobClientStatus in the wrong place. JobClientStatusSummary is the lowest common ancestor that should compute and store this derived state.
The API call happens on the route which is protected by the ember-can check. The associated controllers aren't fetching nodes (with the exception of the jobs/job/clients controller, and this tab should be protected using the can helper -- this is another associated bug for this issue) because they're peaking from the store. The component is not fetching data either. Now, there's one more concern, the computed macro jobClientStatus this should not be making any API calls, if it is then its a bug.
3. There's some more lurking bugs that this PR raises as mentioned in #2.
4. Now that tests are failing and we're circumnavigating currentURL(), I think I need to take a deeper look into this.

Can you give me 2 days to close out the breadcrumbs work so I can take a few hours to review this?

@lgfa29
Copy link
Contributor Author

lgfa29 commented Dec 15, 2021

Sure 👍

@github-actions
Copy link

Ember Test Audit flaky tests

Ember Test Audit detected these flaky tests on main:

  • Acceptance | job dispatch: required meta fields are properly indicated

@lgfa29 lgfa29 merged commit 6b488bd into main Jan 13, 2022
@lgfa29 lgfa29 deleted the ui-fix-acl-job-details branch January 13, 2022 02:26
@github-actions
Copy link

github-actions bot commented Nov 4, 2022

I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants