Lens app source code (#119)

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2020-03-15 09:52:02 +02:00
1 parent 936cbd53d4
commit 1d0815abd2
797 files changed
+73714 -215

No files matched your search

@@ -0,0 +1,23 @@
// Get certificate auth data
import * as fs from "fs";
import * as util from "util";
import config from "../config";
let caData: string = null
export async function getCertificateAuthorityData(encoding = 'utf8'): Promise<string> {
if (caData) {
return caData
}
if (!fs.existsSync(config.KUBERNETES_CA_CERT)) {
caData = config.KUBERNETES_CA_CERT
return caData
}
try {
const ca = await util.promisify(fs.readFile)(config.KUBERNETES_CA_CERT);
return Buffer.from(ca).toString(encoding);
} catch (error) {
return ''
}
}
+39
View File
@@ -0,0 +1,39 @@
// Get cluster info
import config from "../config"
import { kubeRequest } from "./kube-request";
import { IClusterInfo, IClusterConfigMap } from "../common/cluster"
export async function getClusterInfo(): Promise<IClusterInfo> {
const [configMap, kubeVersion, pharosVersion] = await Promise.all([
getClusterConfigMap().catch(() => ({} as IClusterConfigMap)),
getKubeVersion().catch(() => null),
getPharosVersion().catch(() => null),
]);
return {
...configMap,
kubeVersion,
pharosVersion,
};
}
export async function getClusterConfigMap() {
const res = await kubeRequest<{ data: IClusterConfigMap }>({
path: `/api/v1/namespaces/${config.LENS_NAMESPACE}/configmaps/config`,
});
return res.data;
}
export async function getKubeVersion() {
const res = await kubeRequest<{ gitVersion: string }>({
path: "/version",
});
return res.gitVersion.slice(1);
}
export async function getPharosVersion() {
const res = await kubeRequest<{ data: { "pharos-version": string } }>({
path: `/api/v1/namespaces/kube-system/configmaps/pharos-config`,
});
return res ? res.data["pharos-version"] : null;
}
+59
View File
@@ -0,0 +1,59 @@
// Get namespaces
import config from "../config";
import { KubeJsonApiDataList } from "../../client/api/kube-json-api";
import { IKubeRequestParams, kubeRequest } from "./kube-request";
import { reviewResourceAccess } from "./review-resource-access";
import { getServiceAccountToken } from "./get-service-account-token"
export async function getNamespaces(params: Partial<IKubeRequestParams> = {}) {
return kubeRequest<KubeJsonApiDataList>({
...params,
path: "/api/v1/namespaces",
});
}
export async function getAllowedNamespaces(
params: Partial<IKubeRequestParams>,
fallbackNs = config.KUBERNETES_NAMESPACE,
): Promise<string[]> {
try {
const allNamespaces = await getNamespaces(params);
const nsAccessStatuses = await Promise.all(
allNamespaces.items.map(ns => {
const { name } = ns.metadata;
return reviewResourceAccess(params, {
namespace: name,
resource: "pods",
verb: "list",
});
})
);
return allNamespaces.items
.filter((ns, i) => nsAccessStatuses[i].allowed)
.map(ns => ns.metadata.name);
} catch (e) {
const serviceToken = await getServiceAccountToken();
if (!serviceToken) {
return fallbackNs ? [fallbackNs] : [];
}
// fetch namespaces with service-account token (cluster-wide)
// and for every namespace make additional request to check if namespace available for user-token
const allNamespaces = await getNamespaces({
authHeader: `Bearer ${serviceToken}`
});
const nsAccessStatuses = await Promise.all(
allNamespaces.items.map(ns => {
const { name } = ns.metadata;
return reviewResourceAccess(params, {
namespace: name,
resource: "pods",
verb: "list",
});
})
);
return allNamespaces.items
.filter((ns, i) => nsAccessStatuses[i].allowed)
.map(ns => ns.metadata.name);
}
}
@@ -0,0 +1,22 @@
// Get service-account token
import { existsSync, readFile } from "fs";
import { promisify } from "util";
import config from "../config"
const tokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token";
export async function getServiceAccountToken() {
const { SERVICE_ACCOUNT_TOKEN } = config;
if (SERVICE_ACCOUNT_TOKEN) {
return SERVICE_ACCOUNT_TOKEN;
}
if (existsSync(tokenPath)) {
const token = await promisify(readFile)(tokenPath);
return token.toString().trim();
}
return null;
}
+19
View File
@@ -0,0 +1,19 @@
// Check cluster-admin rights for auth-token
// CLI: kubectl auth can-i '*' '*' --all-namespaces
import { reviewResourceAccess } from "./review-resource-access";
import { IKubeRequestParams } from "./kube-request";
export async function isClusterAdmin(params: Partial<IKubeRequestParams>): Promise<boolean> {
try {
const accessCheck = await reviewResourceAccess(params, {
resource: "*",
namespace: "*",
group: "*",
verb: "*",
});
return accessCheck.allowed;
} catch (err) {
return false;
}
}
+58
View File
@@ -0,0 +1,58 @@
// Kubernetes request api helper
import config, { isSecure } from "../config";
import axios, { AxiosError, AxiosRequestConfig } from "axios"
import * as https from "https";
import { getCertificateAuthorityData } from "./get-cert-auth-data";
import { logger, sanitizeHeaders } from "../utils/logger";
import { getServiceAccountToken } from "./get-service-account-token";
export interface IKubeRequestParams extends AxiosRequestConfig {
path: string;
authHeader?: string;
}
export async function kubeRequest<T>(params: IKubeRequestParams): Promise<T> {
const { KUBE_CLUSTER_URL, KUBERNETES_CLIENT_CERT, KUBERNETES_CLIENT_KEY } = config;
const serviceToken = await getServiceAccountToken();
const defaultAuthHeader = serviceToken ? `Bearer ${serviceToken}` : "";
const {
authHeader = defaultAuthHeader,
url = KUBE_CLUSTER_URL,
path = "",
...reqConfig
} = params;
// add access token
reqConfig.headers = Object.assign({}, reqConfig.headers, {
"Content-type": "application/json",
});
if (!KUBERNETES_CLIENT_CERT && authHeader) {
reqConfig.headers["Authorization"] = authHeader;
}
// allow requests to kube-cluster without valid ssl certs..
reqConfig.httpsAgent = new https.Agent({
rejectUnauthorized: isSecure(),
cert: KUBERNETES_CLIENT_CERT,
key: KUBERNETES_CLIENT_KEY,
ca: await getCertificateAuthorityData(),
});
const reqUrl = url + path;
return axios(reqUrl, reqConfig)
.then(res => res.data)
.catch((error: AxiosError<T>) => {
const { message, config } = error;
logger.error(`[KUBE-REQUEST]: ${message}`, {
code: error.code,
method: config.method,
url: config.url,
headers: sanitizeHeaders(config.headers),
params: config.params,
});
throw error;
});
}
@@ -0,0 +1,47 @@
// Get resource access review
// Docs: https://kubernetes.io/docs/reference/access-authn-authz/authorization/
import { IKubeRequestParams, kubeRequest } from "./kube-request";
interface IResourceAccess {
apiVersion: string;
kind: string;
status: IResourceAccessStatus;
}
export interface IResourceAccessStatus {
allowed: boolean;
denied?: boolean;
reason?: string;
evaluationError?: string;
}
interface IResourceAccessAttributes {
group?: string | "*";
resource?: string | "*";
verb?: "get" | "list" | "create" | "update" | "patch" | "watch" | "proxy" | "redirect" | "delete" | "deletecollection" | "*";
namespace?: string | "*";
}
export async function reviewResourceAccess(
params: Partial<IKubeRequestParams> = {},
attrs: IResourceAccessAttributes
): Promise<IResourceAccessStatus> {
try {
const accessReview = await kubeRequest<IResourceAccess>({
...params,
method: "POST",
path: "/apis/authorization.k8s.io/v1/selfsubjectaccessreviews",
data: {
spec: {
resourceAttributes: attrs
}
}
});
return accessReview.status;
} catch (err) {
return {
allowed: false,
reason: err.toString(),
}
}
}
+39
View File
@@ -0,0 +1,39 @@
// Check validity of auth-token
import { kubeRequest } from "./kube-request";
export interface ITokenReview {
apiVersion: string;
kind: string;
status: ITokenReviewStatus;
}
export interface ITokenReviewStatus {
authenticated: boolean;
user: {
username?: string;
uid?: string;
groups?: string[];
};
error?: string[];
}
export async function reviewToken(authToken: string): Promise<ITokenReviewStatus> {
try {
const tokenReview = await kubeRequest<ITokenReview>({
path: "/apis/authentication.k8s.io/v1/tokenreviews",
method: "POST",
data: {
spec: {
token: authToken
}
}
});
return tokenReview.status;
} catch (err) {
return {
authenticated: false,
user: {},
error: [err.toString()],
}
}
}