Skip to content

Commit

Permalink
Merge pull request #1526 from headlamp-k8s/fix-permissions-api
Browse files Browse the repository at this point in the history
frontend: Use the right group and version in KubeObject.getAuthorization
  • Loading branch information
joaquimrocha authored Nov 7, 2023
2 parents 24c98de + bfc4003 commit 22796b0
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ exports[`Storyshots hpa/HpaDetailsView Default 1`] = `
/>
<div
class="MuiGrid-root MuiGrid-item"
/>
>
<button
aria-label="View YAML"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd"
tabindex="0"
title="View YAML"
type="button"
>
<span
class="MuiIconButton-label"
>
<span />
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<div
class="MuiGrid-root MuiGrid-item"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ exports[`Storyshots pdb/PDBDetailsView Default 1`] = `
/>
<div
class="MuiGrid-root MuiGrid-item"
/>
>
<button
aria-label="View YAML"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd"
tabindex="0"
title="View YAML"
type="button"
>
<span
class="MuiIconButton-label"
>
<span />
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<div
class="MuiGrid-root MuiGrid-item"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ exports[`Storyshots PriorityClass/PriorityClassDetailsView Default 1`] = `
/>
<div
class="MuiGrid-root MuiGrid-item"
/>
>
<button
aria-label="View YAML"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd"
tabindex="0"
title="View YAML"
type="button"
>
<span
class="MuiIconButton-label"
>
<span />
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<div
class="MuiGrid-root MuiGrid-item"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ exports[`Storyshots ResourceQuota/ResourceQuotaDetailsView Default 1`] = `
/>
<div
class="MuiGrid-root MuiGrid-item"
/>
>
<button
aria-label="View YAML"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd"
tabindex="0"
title="View YAML"
type="button"
>
<span
class="MuiIconButton-label"
>
<span />
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<div
class="MuiGrid-root MuiGrid-item"
/>
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/lib/k8s/apiProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,26 @@ function multipleApiFactory(
put: repeatFactoryMethod(apiEndpoints, 'put'),
delete: repeatFactoryMethod(apiEndpoints, 'delete'),
isNamespaced: false,
apiInfo: args.map(apiArgs => ({
group: apiArgs[0],
version: apiArgs[1],
resource: apiArgs[2],
})),
};
}

/**
* Describes the API for a certain resource.
*/
export interface ApiInfo {
/** The API group. */
group: string;
/** The API version. */
version: string;
/** The resource name. */
resource: string;
}

// @todo: singleApiFactory should have a return type rather than just what it returns.

/**
Expand Down Expand Up @@ -608,6 +625,7 @@ function singleApiFactory(group: string, version: string, resource: string) {
delete: (name: string, queryParams?: QueryParameters) =>
remove(`${url}/${name}` + asQuery(queryParams)),
isNamespaced: false,
apiInfo: [{ group, version, resource }],
};
}

Expand Down Expand Up @@ -647,6 +665,11 @@ function multipleApiFactoryWithNamespace(
put: repeatFactoryMethod(apiEndpoints, 'put'),
delete: repeatFactoryMethod(apiEndpoints, 'delete'),
isNamespaced: true,
apiInfo: args.map(apiArgs => ({
group: apiArgs[0],
version: apiArgs[1],
resource: apiArgs[2],
})),
};
}

Expand Down Expand Up @@ -699,6 +722,7 @@ function simpleApiFactoryWithNamespace(
delete: (namespace: string, name: string, queryParams?: QueryParameters) =>
remove(`${url(namespace)}/${name}` + asQuery(queryParams)),
isNamespaced: true,
apiInfo: [{ group, version, resource }],
};

if (includeScale) {
Expand Down
108 changes: 85 additions & 23 deletions frontend/src/lib/k8s/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export interface AuthRequestResourceAttrs {
resource?: string;
subresource?: string;
namespace?: string;
version?: string;
group?: string;
verb?: string;
}

// @todo: uses of makeKubeObject somehow end up in an 'any' type.
Expand Down Expand Up @@ -667,10 +670,39 @@ export function makeKubeObject<T extends KubeObjectInterface | KubeEvent>(
return this._class().apiEndpoint.patch(...args);
}

/** Performs a request to check if the user has the given permission.
* @param reResourceAttrs The attributes describing this access request. See https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/self-subject-access-review-v1/#SelfSubjectAccessReviewSpec .
* @returns The result of the access request.
*/
private static async fetchAuthorization(reqResourseAttrs?: AuthRequestResourceAttrs) {
// @todo: We should get the API info from the API endpoint.
const authApiVersions = ['v1', 'v1beta1'];
for (let j = 0; j < authApiVersions.length; j++) {
const authVersion = authApiVersions[j];

try {
return await post(
`/apis/authorization.k8s.io/${authVersion}/selfsubjectaccessreviews`,
{
kind: 'SelfSubjectAccessReview',
apiVersion: `authorization.k8s.io/${authVersion}`,
spec: {
resourceAttributes: reqResourseAttrs,
},
},
false
);
} catch (err) {
// If this is the last attempt or the error is not 404, let it throw.
if ((err as ApiError).status !== 404 || j === authApiVersions.length - 1) {
throw err;
}
}
}
}

static async getAuthorization(verb: string, reqResourseAttrs?: AuthRequestResourceAttrs) {
const resourceAttrs: AuthRequestResourceAttrs & {
verb: string;
} = {
const resourceAttrs: AuthRequestResourceAttrs = {
verb,
...reqResourseAttrs,
};
Expand All @@ -679,37 +711,53 @@ export function makeKubeObject<T extends KubeObjectInterface | KubeEvent>(
resourceAttrs['resource'] = this.pluralName;
}

const spec = {
resourceAttributes: resourceAttrs,
};
// @todo: We should get the API info from the API endpoint.

// If we already have the group, version, and resource, then we can make the request
// without trying the API info, which may have several versions and thus be less optimal.
if (!!resourceAttrs.group && !!resourceAttrs.version && !!resourceAttrs.resource) {
return this.fetchAuthorization(resourceAttrs);
}

// If we don't have the group, version, and resource, then we have to try all of the
// API info versions until we find one that works.
const apiInfo = this.apiEndpoint.apiInfo;
for (let i = 0; i < apiInfo.length; i++) {
const { group, version, resource } = apiInfo[i];
// We only take from the details from the apiInfo if they're missing from the resourceAttrs.
// The idea is that, since this function may also be called from the instance's getAuthorization,
// it may already have the details from the instance's API version.
const attrs = { ...resourceAttrs };

if (!!attrs.resource) {
attrs.resource = resource;
}
if (!!attrs.group) {
attrs.group = group;
}
if (!!attrs.version) {
attrs.version = version;
}

let authResult;

const versions = ['v1', 'v1beta1'];
for (let i = 0; i < versions.length; i++) {
const version = versions[i];
try {
return await post(
`/apis/authorization.k8s.io/${version}/selfsubjectaccessreviews`,
{
kind: 'SelfSubjectAccessReview',
apiVersion: `authorization.k8s.io/${version}`,
spec,
},
false
);
authResult = await this.fetchAuthorization(attrs);
} catch (err) {
// If this is the last attempt or the error is not 404, let it throw.
if ((err as ApiError).status !== 404 || i === versions.length - 1) {
if ((err as ApiError).status !== 404 || i === apiInfo.length - 1) {
throw err;
}
}

if (!!authResult) {
return authResult;
}
}
}

async getAuthorization(verb: string, reqResourseAttrs?: AuthRequestResourceAttrs) {
const resourceAttrs: AuthRequestResourceAttrs & {
name: string;
verb: string;
} = {
const resourceAttrs: AuthRequestResourceAttrs = {
name: this.getName(),
verb,
...reqResourseAttrs,
Expand All @@ -720,6 +768,20 @@ export function makeKubeObject<T extends KubeObjectInterface | KubeEvent>(
resourceAttrs['namespace'] = namespace;
}

// Set up the group and version from the object's API version.
let [group, version] = this.jsonData?.apiVersion.split('/');
if (!version) {
version = group;
group = '';
}

if (!!group) {
resourceAttrs['group'] = group;
}
if (!!version) {
resourceAttrs['version'] = version;
}

return this._class().getAuthorization(verb, resourceAttrs);
}

Expand Down

0 comments on commit 22796b0

Please sign in to comment.