Skip to content

Commit

Permalink
entitlement package
Browse files Browse the repository at this point in the history
Signed-off-by: Kunal Kushwaha <[email protected]>
  • Loading branch information
kunalkushwaha committed Aug 27, 2018
1 parent 3b3da0f commit 5ea2f71
Show file tree
Hide file tree
Showing 15 changed files with 3,632 additions and 3 deletions.
118 changes: 118 additions & 0 deletions util/entitlements/apparmor/apparmor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package apparmor

import (
"io"
"os"
"path"
)

const (
// defaultProfileDirectory is the apparmor default profile's directory
defaultProfileDirectory = "/etc/apparmor.d"
)

var (
// profileDirectory is the file store for apparmor profiles and macros.
profileDirectory = defaultProfileDirectory
)

type networkRawSetup struct {
Denied bool
}

/*
NetworkSetup contains flags and data to configure network rules in AppArmor.
See http://manpages.ubuntu.com/manpages/precise/man5/apparmor.d.5.html
for more information regarding supported protocols, network data types
and domains.
*/
type NetworkSetup struct {
Denied bool
AllowedProtocols []string
Raw networkRawSetup
}

/*
CapabilitiesSetup contains flags and data to configure capability rules in AppArmor.
See http://manpages.ubuntu.com/manpages/precise/man5/apparmor.d.5.html
for more information regarding supported capabilities.
*/
type CapabilitiesSetup struct {
Allowed []string
Denied []string
}

// FilesSetup contains data to configure filesystem access rules in AppArmor.
type FilesSetup struct {
// Denied is a list of filepaths to deny any access to
Denied []string
// ReadOnly is a list of filepaths to restrict to read access only
ReadOnly []string
// NoExec is a list of filepaths for which execution is denied
NoExec []string
}

// ProfileData holds information about the given profile for generation.
type ProfileData struct {
// Name is profile name.
Name string
// Imports defines the apparmor functions to import, before defining the profile.
Imports []string
// InnerImports defines the apparmor functions to import in the profile.
InnerImports []string
// Version is the {major, minor, patch} version of apparmor_parser as a single number.
Version int

// Network defines the network setup we want, see NetworkSetup type definition
Network NetworkSetup

// Capabilities defines the capabilities setup we want, see CapabiltitiesSetup type definition
Capabilities CapabilitiesSetup

// Files defines the files access setup we want, see FilesSetup type definition
Files FilesSetup
}

// NewEmptyProfileData creates an empty ProfileData object with its name.
func NewEmptyProfileData(name string) *ProfileData {
return &ProfileData{Name: name}
}

// macrosExists checks if the passed macro exists.
func macroExists(m string) bool {
_, err := os.Stat(path.Join(profileDirectory, m))
return err == nil
}

// SetAppArmorProfileDirectory sets AppArmor's profile directory
func SetAppArmorProfileDirectory(path string) {
profileDirectory = path
}

// GenerateAppArmorProfile creates an AppArmor profile and writes it to the io.Writer argument
func GenerateAppArmorProfile(p ProfileData, out io.Writer) error {
aaProfile, err := NewParse("apparmor_profile", baseCustomTemplate)
if err != nil {
return err
}

if macroExists("tunables/global") {
p.Imports = append(p.Imports, "#include <tunables/global>")
} else {
p.Imports = append(p.Imports, "@{PROC}=/proc/")
}

if macroExists("abstractions/base") {
p.InnerImports = append(p.InnerImports, "#include <abstractions/base>")
}

// FIXME: add `aaparser` pkg
/*
ver, err := aaparser.GetVersion()
if err != nil {
return err
}
*/

return aaProfile.Execute(out, p)
}
149 changes: 149 additions & 0 deletions util/entitlements/apparmor/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package apparmor

import (
"bytes"
"encoding/json"
"strings"
"text/template"
)

/* See profile language http://wiki.apparmor.net/index.php/QuickProfileLanguage
* This profile is the base template of Moby, customized to allow entitlements to configure
* network, capabilities and file accesses
*/
const baseCustomTemplate = `
{{range $value := .Imports}}
{{$value}}
{{end}}
profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
{{range $value := .InnerImports}}
{{$value}}
{{end}}
{{if .Network.Denied}}
deny network,
{{else}}
{{if .Network.AllowedProtocols}}
{{range $value := .Network.AllowedProtocols}} network inet {{$value}},
{{end}}
{{else}}
network,
{{end}}
{{if .Network.Raw.Denied}}
deny network raw,
{{end}}
{{end}}
{{range $value := .Capabilities.Allowed}} capabilty {{$value}},
{{end}}
{{range $value := .Capabilities.Denied}} deny capability {{$value}},
{{end}}
{{range $value := .Files.Denied}} deny {{$value}} rwamklx,
{{end}}
{{range $value := .Files.ReadOnly}} deny {{$value}} wkal,
{{end}}
{{range $value := .Files.NoExec}} deny {{$value}} x,
{{end}}
file,
umount,
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kcore rwklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/** rwklx,
deny /sys/kernel/security/** rwklx,
{{if ge .Version 208095}}
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read) peer={{.Name}},
{{end}}
}
`

// basicFunctions are the set of initial
// functions provided to every template.
var basicFunctions = template.FuncMap{
"json": func(v interface{}) string {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
enc.Encode(v)
// Remove the trailing new line added by the encoder
return strings.TrimSpace(buf.String())
},
"split": strings.Split,
"join": strings.Join,
"title": strings.Title,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"pad": padWithSpace,
"truncate": truncateWithLength,
}

// HeaderFunctions are used to created headers of a table.
// This is a replacement of basicFunctions for header generation
// because we want the header to remain intact.
// Some functions like `split` are irrelevant so not added.
var HeaderFunctions = template.FuncMap{
"json": func(v string) string {
return v
},
"title": func(v string) string {
return v
},
"lower": func(v string) string {
return v
},
"upper": func(v string) string {
return v
},
"truncate": func(v string, l int) string {
return v
},
}

// Parse creates a new anonymous template with the basic functions
// and parses the given format.
func Parse(format string) (*template.Template, error) {
return NewParse("", format)
}

// NewParse creates a new tagged template with the basic functions
// and parses the given format.
func NewParse(tag, format string) (*template.Template, error) {
return template.New(tag).Funcs(basicFunctions).Parse(format)
}

// padWithSpace adds whitespace to the input if the input is non-empty
func padWithSpace(source string, prefix, suffix int) string {
if source == "" {
return source
}
return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix)
}

// truncateWithLength truncates the source string up to the length provided by the input
func truncateWithLength(source string, length int) string {
if len(source) < length {
return source
}
return source[:length]
}
28 changes: 28 additions & 0 deletions util/entitlements/entitlement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package entitlements

import (
"reflect"
"testing"

"github.com/moby/buildkit/util/entitlements/osdefs"
"github.com/moby/buildkit/util/entitlements/testutils"
"github.com/stretchr/testify/require"
)

func TestProfileSecurityConfined(t *testing.T) {
testSyscall := osdefs.SysExit
ociProfile := NewOCIProfile(testutils.TestSpec(), "test-profile")

require.NotNil(t, ociProfile.OCI)
require.NotNil(t, ociProfile.OCI.Linux)
require.NotNil(t, ociProfile.OCI.Linux.Seccomp)

syscalls := []osdefs.Syscall{testSyscall}
ociProfile.AllowSyscalls(syscalls...)

seccompProfileWithTestSys := *ociProfile.OCI.Linux.Seccomp

ociProfile.AllowSyscalls(syscalls...)
didAdd := reflect.DeepEqual(seccompProfileWithTestSys, *ociProfile.OCI.Linux.Seccomp)
require.True(t, didAdd, "Syscall was not already added to the seccomp profile")
}
3 changes: 0 additions & 3 deletions util/entitlements/entitlements.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func Parse(s string) (Entitlement, error) {

func WhiteList(allowed, supported []Entitlement) (Set, error) {
m := map[Entitlement]struct{}{}

var supm Set
if supported != nil {
var err error
Expand All @@ -42,7 +41,6 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
return nil, err
}
}

for _, e := range allowed {
e, err := Parse(string(e))
if err != nil {
Expand All @@ -55,7 +53,6 @@ func WhiteList(allowed, supported []Entitlement) (Set, error) {
}
m[e] = struct{}{}
}

for e := range defaults {
m[e] = struct{}{}
}
Expand Down
Loading

0 comments on commit 5ea2f71

Please sign in to comment.