Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ Ensure that the Workload API socket directory is shared with the SPIFFE CSI
Driver via a `hostPath` volume. The directory backing `emptyDir` volumes are
tied to the pod instance and invalidated when the pod is restarted.

### Permission Denied accessing Workload API directory

This may be caused by SELinux in environments like OpenShift. The SPIFFE CSI
Driver attempts to set the container file label on the Workload API socket
directory on startup. Check the logs to see if it was successful. To be
successful, the Workload API socket directory must be mounted read-write into
the CSI driver container.

## Reporting a Vulnerability

Vulnerabilities can be reported by sending an email to security@spiffe.io. A
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
35 changes: 32 additions & 3 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,26 @@ import (

"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/go-logr/logr"
"github.com/opencontainers/selinux/go-selinux"
"github.com/spiffe/spiffe-csi/internal/version"
"github.com/spiffe/spiffe-csi/pkg/logkeys"
"github.com/spiffe/spiffe-csi/pkg/mount"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

const (
seLinuxContainerFileLabel = "container_file_t"
)

var (
// We replace these in tests since bind mounting generally requires root.
bindMountRW = mount.BindMountRW
unmount = mount.Unmount
isMountPoint = mount.IsMountPoint
bindMountRW = mount.BindMountRW
unmount = mount.Unmount
isMountPoint = mount.IsMountPoint
chcon = selinux.Chcon
seLinuxEnabled = selinux.GetEnabled
seLinuxEnforceMode = selinux.EnforceMode
)

// Config is the configuration for the driver
Expand Down Expand Up @@ -49,6 +57,27 @@ func New(config Config) (*Driver, error) {
case config.WorkloadAPISocketDir == "":
return nil, errors.New("workload API socket directory is required")
}

// Set the SELinux label on the workload API directory. This allows the
// mount to be used within OpenShift, for example. This will fail if the
// Workload API socket directory is mounted read-only, but that will only
// result in a failure if SELinux is enabled and enforcing.
seLinuxEnabled := seLinuxEnabled()
seLinuxEnforceMode := seLinuxEnforceMode()
seLinuxProcessLabel, seLinuxFileLabel := selinux.ContainerLabels()
config.Log.Info("SELinux status",
"enabled", seLinuxEnabled,
"enforceMode", seLinuxEnforceMode,
"processLabel", seLinuxProcessLabel,
"fileLabel", seLinuxFileLabel,
)
if seLinuxEnabled && seLinuxEnforceMode == selinux.Enforcing {
if err := chcon(config.WorkloadAPISocketDir, seLinuxContainerFileLabel, true); err != nil {
return nil, fmt.Errorf("failed to set the container file label on the Workload API socket directory: %w", err)
}
config.Log.Info("Set the container file label on the Workload API socket directory")
}

return &Driver{
log: config.Log,
nodeID: config.NodeID,
Expand Down
32 changes: 32 additions & 0 deletions pkg/driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/go-logr/logr"
"github.com/opencontainers/selinux/go-selinux"
"github.com/spiffe/spiffe-csi/internal/version"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -34,31 +35,42 @@ func init() {
unmount = func(dst string) error {
return os.Remove(metaPath(dst))
}
chcon = writeSELinuxLabel
seLinuxEnabled = func() bool {
return true
}
seLinuxEnforceMode = func() int {
return selinux.Enforcing
}
}

func TestNew(t *testing.T) {
workloadAPISocketDir := t.TempDir()

t.Run("node ID is required", func(t *testing.T) {
_, err := New(Config{
Log: logr.Discard(),
WorkloadAPISocketDir: workloadAPISocketDir,
})
require.EqualError(t, err, "node ID is required")
})

t.Run("workload API socket directory is required", func(t *testing.T) {
_, err := New(Config{
Log: logr.Discard(),
NodeID: testNodeID,
})
require.EqualError(t, err, "workload API socket directory is required")
})

t.Run("success", func(t *testing.T) {
_, err := New(Config{
Log: logr.Discard(),
NodeID: testNodeID,
WorkloadAPISocketDir: workloadAPISocketDir,
})
require.NoError(t, err)
assertSELinuxLabelWritten(t, workloadAPISocketDir)
})
}

Expand Down Expand Up @@ -469,6 +481,13 @@ func assertNotMounted(t *testing.T, targetPath string) {
assert.Error(t, err, "should not be mounted")
}

func assertSELinuxLabelWritten(t *testing.T, fpath string) {
label, err := readSELinuxLabel(fpath)
if assert.NoError(t, err, "failed to read selinux label file") {
assert.Equal(t, "container_file_t-recursive-true", label)
}
}

func readMeta(targetPath string) (string, error) {
data, err := os.ReadFile(metaPath(targetPath))
return string(data), err
Expand All @@ -478,10 +497,23 @@ func writeMeta(targetPath string, meta string) error {
return os.WriteFile(metaPath(targetPath), []byte(meta), 0600)
}

func readSELinuxLabel(fpath string) (string, error) {
data, err := os.ReadFile(seLinuxLabelPath(fpath))
return string(data), err
}

func writeSELinuxLabel(fpath string, label string, recursive bool) error {
return os.WriteFile(seLinuxLabelPath(fpath), []byte(fmt.Sprintf("%s-recursive-%t", label, recursive)), 0600)
}

func metaPath(targetPath string) string {
return filepath.Join(targetPath, "meta")
}

func seLinuxLabelPath(targetPath string) string {
return filepath.Join(targetPath, "selinux-label")
}

func dumpIt(t *testing.T, when, dir string) {
t.Logf(">>>>>>>>>> DUMPING %s %s", when, dir)
assert.NoError(t, filepath.Walk(dir, filepath.WalkFunc(
Expand Down
6 changes: 4 additions & 2 deletions test/config/spiffe-csi-driver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ spec:
fieldPath: spec.nodeName
volumeMounts:
# The volume containing the SPIRE agent socket. The SPIFFE CSI
# driver will mount this directory into containers.
# driver will mount this directory into containers. This
# mount must be read-write in order for the driver to set the
# adjust the SELinux labels on the contents.
- mountPath: /spire-agent-socket
name: spire-agent-socket-dir
readOnly: true
readOnly: false
# The volume that will contain the CSI driver socket shared
# with the kubelet and the driver registrar.
- mountPath: /spiffe-csi
Expand Down
12 changes: 11 additions & 1 deletion test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ esac


cleanup() {
if [ -z "${SUCCESS}" ]; then
"${KUBECTL}" logs deployment/spire-server -nspire-system --all-containers=true || true
"${KUBECTL}" logs daemonset/spire-agent -nspire-system --all-containers=true || true
"${KUBECTL}" logs daemonset/spiffe-csi-driver -nspire-system --all-containers=true || true
"${KUBECTL}" logs deployment/test-workload-1 --all-containers=true || true
"${KUBECTL}" logs deployment/test-workload-2 --all-containers=true || true
fi

delete-cluster
rm -rf "${TMPDIR}"
}
Expand Down Expand Up @@ -104,6 +112,8 @@ apply-yaml() {
"${KUBECTL}" rollout status -w --timeout=1m -nspire-system deployment/spire-server
echo "Waiting for SPIRE agent rollout..."
"${KUBECTL}" rollout status -w --timeout=1m -nspire-system daemonset/spire-agent
echo "Waiting for SPIFFE CSI driver rollout..."
"${KUBECTL}" rollout status -w --timeout=1m -nspire-system daemonset/spiffe-csi-driver
echo "Waiting for test workload 1 rollout..."
"${KUBECTL}" rollout status -w --timeout=1m deployment/test-workload-1
echo "Waiting for test workload 2 rollout..."
Expand Down Expand Up @@ -159,5 +169,5 @@ apply-yaml
register-workload
check-workload-status "test-workload-1"
check-workload-status "test-workload-2"
"${KUBECTL}" logs -nspire-system daemonset/spiffe-csi-driver -c spiffe-csi-driver
SUCCESS=1
echo "Done."