Skip to content

Commit

Permalink
feat: Forbid access to remote/applied objects and all secrets when no…
Browse files Browse the repository at this point in the history
…t an admin
  • Loading branch information
codablock committed Aug 14, 2023
1 parent 3f0f0a3 commit 96d1013
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 18 deletions.
52 changes: 47 additions & 5 deletions pkg/webui/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
kluctlv1 "github.com/kluctl/kluctl/v2/api/v1beta1"
"github.com/kluctl/kluctl/v2/pkg/results"
"github.com/kluctl/kluctl/v2/pkg/status"
"github.com/kluctl/kluctl/v2/pkg/types"
"github.com/kluctl/kluctl/v2/pkg/types/k8s"
"github.com/kluctl/kluctl/v2/pkg/types/result"
"github.com/kluctl/kluctl/v2/pkg/utils/uo"
"io/fs"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"net"
Expand Down Expand Up @@ -282,6 +284,28 @@ func (s *CommandResultsServer) redactSensitiveVars(user *User, d *types.Deployme
}
}

func (s *CommandResultsServer) checkObjectAccess(c *gin.Context, user *User, o *uo.UnstructuredObject, objectType string) (bool, *uo.UnstructuredObject) {
if user.IsAdmin {
// everything allowed
return true, o
}
if o == nil {
return true, o
}

// non-admins can only see the rendered version
if objectType != "rendered" {
return false, o
}

// but no secrets
if o.GetK8sRef().GroupKind() == (schema.GroupKind{Kind: "Secret"}) {
return false, nil
}

return true, o
}

func (s *CommandResultsServer) getCommandResult(c *gin.Context) {
var params resultIdParam

Expand All @@ -308,6 +332,13 @@ func (s *CommandResultsServer) getCommandResult(c *gin.Context) {

s.redactSensitiveVars(user, sr.Deployment)

for i, _ := range sr.Objects {
o := &sr.Objects[i]
_, o.Rendered = s.checkObjectAccess(c, user, o.Rendered, "rendered")
_, o.Remote = s.checkObjectAccess(c, user, o.Remote, "remote")
_, o.Applied = s.checkObjectAccess(c, user, o.Applied, "applied")
}

c.JSON(http.StatusOK, sr)
}

Expand Down Expand Up @@ -359,16 +390,26 @@ func (s *CommandResultsServer) getCommandResultObject(c *gin.Context) {
return
}

user := s.auth.getUser(c)

var ok bool
var o2 *uo.UnstructuredObject
switch objectType.ObjectType {
case "rendered":
o2 = found.Rendered
ok, o2 = s.checkObjectAccess(c, user, found.Rendered, objectType.ObjectType)
case "remote":
o2 = found.Remote
ok, o2 = s.checkObjectAccess(c, user, found.Remote, objectType.ObjectType)
case "applied":
o2 = found.Applied
ok, o2 = s.checkObjectAccess(c, user, found.Applied, objectType.ObjectType)
default:
c.AbortWithStatus(http.StatusNotFound)
return
}
if o2 == nil {

if !ok {
c.AbortWithStatus(http.StatusForbidden)
return
} else if o2 == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
Expand Down Expand Up @@ -409,7 +450,8 @@ func (s *CommandResultsServer) doModifyKluctlDeployment(c *gin.Context, clusterI
return
}

kc, err := ca.getClient(user.RbacUser, nil)
rbacUser := s.auth.getRbacUser(user)
kc, err := ca.getClient(rbacUser, nil)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
Expand Down
11 changes: 7 additions & 4 deletions pkg/webui/ui/src/components/result-view/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Box, Divider, IconButton, Paper, Tab, ThemeProvider, Typography, useTheme } from "@mui/material";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { TabContext, TabList, TabPanel } from "@mui/lab";
import { CloseIcon } from "../../icons/Icons";
import { light } from "../theme";
import { User } from "../../api";
import { UserContext } from "../App";

export interface SidePanelTab {
label: string
Expand All @@ -11,7 +13,7 @@ export interface SidePanelTab {

export interface SidePanelProvider {
buildSidePanelTitle(): React.ReactNode
buildSidePanelTabs(): SidePanelTab[]
buildSidePanelTabs(user?: User): SidePanelTab[]
}

export interface SidePanelProps {
Expand All @@ -21,14 +23,15 @@ export interface SidePanelProps {

export function useSidePanelTabs(provider?: SidePanelProvider) {
const [selectedTab, setSelectedTab] = useState<string>();
const user = useContext(UserContext)

const handleTabChange = useCallback((_e: React.SyntheticEvent, value: string) => {
setSelectedTab(value);
}, []);

const tabs = useMemo(
() => provider?.buildSidePanelTabs() || [],
[provider]
() => provider?.buildSidePanelTabs(user) || [],
[provider, user]
);

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions pkg/webui/ui/src/components/result-view/nodes/NodeData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Box, Divider, Typography } from "@mui/material";
import { CommandResultProps } from "../CommandResultView";
import { ChangesTable } from "../ChangesTable";
import { ErrorsTable } from "../../ErrorsTable";
import { ObjectType } from "../../../api";
import { ObjectType, User } from "../../../api";
import { ObjectYaml } from "../../ObjectYaml";
import { StatusLine } from "../CommandResultStatusLine";
import { SidePanelProvider, SidePanelTab } from "../SidePanel";
Expand Down Expand Up @@ -110,7 +110,7 @@ export abstract class NodeData implements SidePanelProvider {

abstract buildIcon(): [React.ReactNode, string]

abstract buildSidePanelTabs(): SidePanelTab[]
abstract buildSidePanelTabs(user: User): SidePanelTab[]

buildStatusLine(): React.ReactNode {
return <StatusLine
Expand Down
16 changes: 9 additions & 7 deletions pkg/webui/ui/src/components/result-view/nodes/ObjectNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ObjectRef } from "../../../models";
import { NodeData } from "./NodeData";
import { PublishedWithChanges, Settings, SettingsEthernet, SmartToy, SvgIconComponent } from "@mui/icons-material";
import { PropertiesTable } from "../../PropertiesTable";
import { findObjectByRef, ObjectType } from "../../../api";
import { findObjectByRef, ObjectType, User } from "../../../api";
import { CommandResultProps } from "../CommandResultView";
import { SidePanelTab } from "../SidePanel";
import { BracketsCurlyIcon } from '../../../icons/Icons';
Expand Down Expand Up @@ -40,7 +40,7 @@ export class ObjectNodeData extends NodeData {
return [<BracketsCurlyIcon />, snStr]
}

buildSidePanelTabs(): SidePanelTab[] {
buildSidePanelTabs(user?: User): SidePanelTab[] {
const tabs = [
{ label: "Summary", content: this.buildSummaryPage() }
]
Expand All @@ -51,11 +51,13 @@ export class ObjectNodeData extends NodeData {
tabs.push({ label: "Rendered", content: this.buildObjectPage(this.objectRef, ObjectType.Rendered) })
}

if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.remote) {
tabs.push({ label: "Remote", content: this.buildObjectPage(this.objectRef, ObjectType.Remote) })
}
if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.applied) {
tabs.push({ label: "Applied", content: this.buildObjectPage(this.objectRef, ObjectType.Applied) })
if (user?.isAdmin) {
if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.remote) {
tabs.push({ label: "Remote", content: this.buildObjectPage(this.objectRef, ObjectType.Remote) })
}
if (findObjectByRef(this.props.commandResult.objects, this.objectRef)?.applied) {
tabs.push({ label: "Applied", content: this.buildObjectPage(this.objectRef, ObjectType.Applied) })
}
}

return tabs
Expand Down

0 comments on commit 96d1013

Please sign in to comment.