interfaces/buitlin: add network-bind interface, simplify work required to add more #591

Merged
merged 2 commits into from Mar 8, 2016
Jump to file or symbol
Failed to load files and symbols.
+341 −101
Split
@@ -25,7 +25,8 @@ import (
var allInterfaces = []interfaces.Interface{
&BoolFileInterface{},
- &NetworkInterface{},
+ NewNetworkInterface(),
+ NewNetworkBindInterface(),
}
// Interfaces returns all of the built-in interfaces.
@@ -33,5 +33,6 @@ var _ = Suite(&AllSuite{})
func (s *AllSuite) TestInterfaces(c *C) {
all := builtin.Interfaces()
c.Check(all, Contains, &builtin.BoolFileInterface{})
- c.Check(all, Contains, &builtin.NetworkInterface{})
+ c.Check(all, DeepContains, builtin.NewNetworkInterface())
+ c.Check(all, DeepContains, builtin.NewNetworkBindInterface())
}
@@ -20,11 +20,110 @@
package builtin
import (
+ "fmt"
"path/filepath"
+
+ "github.com/ubuntu-core/snappy/interfaces"
)
type evalSymlinksFn func(string) (string, error)
// evalSymlinks is either filepath.EvalSymlinks or a mocked function for
// applicable for testing.
var evalSymlinks = filepath.EvalSymlinks
+
+type commonInterface struct {
+ name string
+ connectedPlugAppArmor string
+ connectedPlugSecComp string
+ reservedForOS bool
+}
+
+// Name returns the interface name.
+func (iface *commonInterface) Name() string {
+ return iface.name
+}
+
+// SanitizeSlot checks and possibly modifies a slot.
+//
+// If the reservedForOS flag is set then only slots on the "ubuntu-core" snap
+// are allowed.
+func (iface *commonInterface) SanitizeSlot(slot *interfaces.Slot) error {
+ if iface.Name() != slot.Interface {
+ panic(fmt.Sprintf("slot is not of interface %q", iface.Name()))
+ }
+ if iface.reservedForOS && slot.Snap != "ubuntu-core" {
+ return fmt.Errorf("%s slots are reserved for the operating system snap", iface.name)
+ }
+ return nil
+}
+
+// SanitizePlug checks and possibly modifies a plug.
+func (iface *commonInterface) SanitizePlug(plug *interfaces.Plug) error {
+ if iface.Name() != plug.Interface {
+ panic(fmt.Sprintf("plug is not of interface %q", iface.Name()))
+ }
+ // NOTE: currently we don't check anything on the plug side.
+ return nil
+}
+
+// PermanentPlugSnippet returns the snippet of text for the given security
+// system that is used during the whole lifetime of affected applications,
+// whether the plug is connected or not.
+//
+// Plugs don't get any permanent security snippets.
+func (iface *commonInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// ConnectedPlugSnippet returns the snippet of text for the given security
+// system that is used by affected application, while a specific connection
+// between a plug and a slot exists.
+//
+// Connected plugs get the static seccomp and apparmor blobs defined by the
+// instance variables. They are not really connection specific in this case.
+func (iface *commonInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor:
+ return []byte(iface.connectedPlugAppArmor), nil
+ case interfaces.SecuritySecComp:
+ return []byte(iface.connectedPlugSecComp), nil
+ case interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// PermanentSlotSnippet returns the snippet of text for the given security
+// system that is used during the whole lifetime of affected applications,
+// whether the slot is connected or not.
+//
+// Slots don't get any permanent security snippets.
+func (iface *commonInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
+
+// ConnectedSlotSnippet returns the snippet of text for the given security
+// system that is used by affected application, while a specific connection
+// between a plug and a slot exists.
+//
+// Slots don't get any per-connection security snippets.
+func (iface *commonInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
+ switch securitySystem {
+ case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
+ return nil, nil
+ default:
+ return nil, interfaces.ErrUnknownSecurity
+ }
+}
@@ -19,14 +19,10 @@
package builtin
-import (
- "fmt"
-
- "github.com/ubuntu-core/snappy/interfaces"
-)
+import "github.com/ubuntu-core/snappy/interfaces"
// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network
-const connectedPlugAppArmor = `
+const networkConnectedPlugAppArmor = `
# Description: Can access the network as a client.
# Usage: common
#include <abstractions/nameservice>
@@ -36,7 +32,7 @@ const connectedPlugAppArmor = `
`
// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/network
-const connectedPlugSecComp = `
+const networkConnectedPlugSecComp = `
# Description: Can access the network as a client.
# Usage: common
connect
@@ -64,96 +60,12 @@ socket
#socketcall
`
-// NetworkInterface implements the "network" interface.
-//
-// Snaps that have a connected plug of this type can access the network as a
-// client. The OS snap will have the only slot of this type.
-//
-// Usage: common
-type NetworkInterface struct{}
-
-// Name returns the string "network".
-func (iface *NetworkInterface) Name() string {
- return "network"
-}
-
-// SanitizeSlot checks and possibly modifies a slot.
-func (iface *NetworkInterface) SanitizeSlot(slot *interfaces.Slot) error {
- if iface.Name() != slot.Interface {
- panic(fmt.Sprintf("slot is not of interface %q", iface.Name()))
- }
- if slot.Snap != "ubuntu-core" {
- return fmt.Errorf("network slots are reserved for the operating system snap")
- }
- return nil
-}
-
-// SanitizePlug checks and possibly modifies a plug.
-func (iface *NetworkInterface) SanitizePlug(plug *interfaces.Plug) error {
- if iface.Name() != plug.Interface {
- panic(fmt.Sprintf("plug is not of interface %q", iface.Name()))
- }
- // NOTE: currently we don't check anything on the plug side.
- return nil
-}
-
-// PermanentPlugSnippet returns the snippet of text for the given security
-// system that is used during the whole lifetime of affected applications,
-// whether the plug is connected or not.
-//
-// Plugs don't get any permanent security snippets.
-func (iface *NetworkInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) {
- switch securitySystem {
- case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
- return nil, nil
- default:
- return nil, interfaces.ErrUnknownSecurity
- }
-}
-
-// ConnectedPlugSnippet returns the snippet of text for the given security
-// system that is used by affected application, while a specific connection
-// between a plug and a slot exists.
-//
-// Connected plugs get the static seccomp and apparmor blobs defined at the top
-// of the file. They are not really connection specific in this case.
-func (iface *NetworkInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
- switch securitySystem {
- case interfaces.SecurityAppArmor:
- return []byte(connectedPlugAppArmor), nil
- case interfaces.SecuritySecComp:
- return []byte(connectedPlugSecComp), nil
- case interfaces.SecurityDBus, interfaces.SecurityUDev:
- return nil, nil
- default:
- return nil, interfaces.ErrUnknownSecurity
- }
-}
-
-// PermanentSlotSnippet returns the snippet of text for the given security
-// system that is used during the whole lifetime of affected applications,
-// whether the slot is connected or not.
-//
-// Slots don't get any permanent security snippets.
-func (iface *NetworkInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
- switch securitySystem {
- case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
- return nil, nil
- default:
- return nil, interfaces.ErrUnknownSecurity
- }
-}
-
-// ConnectedSlotSnippet returns the snippet of text for the given security
-// system that is used by affected application, while a specific connection
-// between a plug and a slot exists.
-//
-// Slots don't get any per-connection security snippets.
-func (iface *NetworkInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) {
- switch securitySystem {
- case interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityDBus, interfaces.SecurityUDev:
- return nil, nil
- default:
- return nil, interfaces.ErrUnknownSecurity
+// NewNetworkInterface returns a new "network" interface.
+func NewNetworkInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "network",
+ connectedPlugAppArmor: networkConnectedPlugAppArmor,
+ connectedPlugSecComp: networkConnectedPlugSecComp,
+ reservedForOS: true,
}
}
@@ -0,0 +1,101 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package builtin
+
+import (
+ "github.com/ubuntu-core/snappy/interfaces"
+)
+
+// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network-bind
+const networkBindConnectedPlugAppArmor = `
+# Description: Can access the network as a server.
+# Usage: common
+#include <abstractions/nameservice>
+#include <abstractions/ssl_certs>
+
+# These probably shouldn't be something that apps should use, but this offers
+# no information disclosure since the files are in the read-only part of the
+# system.
+/etc/hosts.deny r,
+/etc/hosts.allow r,
+
+@{PROC}/sys/net/core/somaxconn r,
+@{PROC}/sys/net/ipv4/ip_local_port_range r,
+
+# LP: #1496906: java apps need these for some reason and they leak the IPv6 IP
+# addresses and routes. Until we find another way to handle them (see the bug
+# for some options), we need to allow them to avoid developer confusion.
+@{PROC}/@{pid}/net/if_inet6 r,
+@{PROC}/@{pid}/net/ipv6_route r,
+
+# java apps request this but seem to work fine without it. Netlink sockets
+# are used to talk to kernel subsystems though and since apps run as root,
+# allowing blanket access needs to be carefully considered. Kernel capabilities
+# checks (which apparmor mediates) *should* be enough to keep abuse down,
+# however Linux capabilities can be quite broad and there have been CVEs in
+# this area. The issue is complicated because reservied policy groups like
+# 'network-admin' and 'network-firewall' have legitimate use for this rule,
+# however a network facing server shouldn't typically be running with these
+# policy groups. For now, explicitly deny to silence the denial. LP: #1499897
+deny network netlink dgram,
+`
+
+// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/network-bind
+const networkBindConnectedPlugSecComp = `
+# Description: Can access the network as a server.
+# Usage: common
+accept
+accept4
+bind
+connect
+getpeername
+getsockname
+getsockopt
+listen
+recv
+recvfrom
+recvmmsg
+recvmsg
+send
+sendmmsg
+sendmsg
+sendto
+setsockopt
+shutdown
+
+# LP: #1446748 - limit this to AF_INET/AF_INET6
+socket
+
+# This is an older interface and single entry point that can be used instead
+# of socket(), bind(), connect(), etc individually. While we could allow it,
+# we wouldn't be able to properly arg filter socketcall for AF_INET/AF_INET6
+# when LP: #1446748 is implemented.
+#socketcall
+`
+
+// NewNetworkBindInterface returns a new "network-bind" interface.
+func NewNetworkBindInterface() interfaces.Interface {
+ return &commonInterface{
+ name: "network-bind",
+ connectedPlugAppArmor: networkBindConnectedPlugAppArmor,
+ connectedPlugSecComp: networkBindConnectedPlugSecComp,
+ reservedForOS: true,
+ }
+}
Oops, something went wrong.