2016-12-23 02:37:10 +00:00
// +build linux
/ *
Copyright 2016 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package dockertools
import (
"fmt"
"net"
"path"
"strconv"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
2017-01-17 03:38:19 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2016-12-23 02:37:10 +00:00
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/events"
"k8s.io/kubernetes/pkg/kubelet/network"
"k8s.io/kubernetes/pkg/kubelet/network/mock_network"
"k8s.io/kubernetes/pkg/security/apparmor"
utilstrings "k8s.io/kubernetes/pkg/util/strings"
)
func TestGetSecurityOpts ( t * testing . T ) {
const containerName = "bar"
pod := func ( annotations map [ string ] string ) * v1 . Pod {
p := makePod ( "foo" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : containerName } ,
} ,
} )
p . Annotations = annotations
return p
}
tests := [ ] struct {
msg string
pod * v1 . Pod
expectedOpts [ ] string
} { {
msg : "No security annotations" ,
pod : pod ( nil ) ,
expectedOpts : [ ] string { "seccomp=unconfined" } ,
} , {
msg : "Seccomp default" ,
pod : pod ( map [ string ] string {
v1 . SeccompContainerAnnotationKeyPrefix + containerName : "docker/default" ,
} ) ,
expectedOpts : nil ,
} , {
msg : "AppArmor runtime/default" ,
pod : pod ( map [ string ] string {
apparmor . ContainerAnnotationKeyPrefix + containerName : apparmor . ProfileRuntimeDefault ,
} ) ,
expectedOpts : [ ] string { "seccomp=unconfined" } ,
} , {
msg : "AppArmor local profile" ,
pod : pod ( map [ string ] string {
apparmor . ContainerAnnotationKeyPrefix + containerName : apparmor . ProfileNamePrefix + "foo" ,
} ) ,
expectedOpts : [ ] string { "seccomp=unconfined" , "apparmor=foo" } ,
} , {
msg : "AppArmor and seccomp profile" ,
pod : pod ( map [ string ] string {
v1 . SeccompContainerAnnotationKeyPrefix + containerName : "docker/default" ,
apparmor . ContainerAnnotationKeyPrefix + containerName : apparmor . ProfileNamePrefix + "foo" ,
} ) ,
expectedOpts : [ ] string { "apparmor=foo" } ,
} }
dm , _ := newTestDockerManagerWithVersion ( "1.11.1" , "1.23" )
for i , test := range tests {
securityOpts , err := dm . getSecurityOpts ( test . pod , containerName )
assert . NoError ( t , err , "TestCase[%d]: %s" , i , test . msg )
opts , err := dm . fmtDockerOpts ( securityOpts )
assert . NoError ( t , err , "TestCase[%d]: %s" , i , test . msg )
assert . Len ( t , opts , len ( test . expectedOpts ) , "TestCase[%d]: %s" , i , test . msg )
for _ , opt := range test . expectedOpts {
assert . Contains ( t , opts , opt , "TestCase[%d]: %s" , i , test . msg )
}
}
}
func TestSeccompIsUnconfinedByDefaultWithDockerV110 ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.10.1" , "1.22" )
// We want to capture events.
recorder := record . NewFakeRecorder ( 20 )
dm . recorder = recorder
pod := makePod ( "foo" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar" } ,
} ,
} )
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . Contains ( t , newContainer . HostConfig . SecurityOpt , "seccomp:unconfined" , "Pods with Docker versions >= 1.10 must not have seccomp disabled by default" )
cid := utilstrings . ShortenString ( fakeDocker . Created [ 1 ] , 12 )
assert . NoError ( t , expectEvent ( recorder , v1 . EventTypeNormal , events . CreatedContainer ,
fmt . Sprintf ( "Created container with docker id %s; Security:[seccomp=unconfined]" , cid ) ) )
}
func TestUnconfinedSeccompProfileWithDockerV110 ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.10.1" , "1.22" )
pod := makePod ( "foo4" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar4" } ,
} ,
} )
pod . Annotations = map [ string ] string {
v1 . SeccompPodAnnotationKey : "unconfined" ,
}
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo4_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar4\\.[a-f0-9]+_foo4_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . Contains ( t , newContainer . HostConfig . SecurityOpt , "seccomp:unconfined" , "Pods created with a secccomp annotation of unconfined should have seccomp:unconfined." )
}
func TestDefaultSeccompProfileWithDockerV110 ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.10.1" , "1.22" )
pod := makePod ( "foo1" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar1" } ,
} ,
} )
pod . Annotations = map [ string ] string {
v1 . SeccompPodAnnotationKey : "docker/default" ,
}
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo1_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar1\\.[a-f0-9]+_foo1_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . NotContains ( t , newContainer . HostConfig . SecurityOpt , "seccomp:unconfined" , "Pods created with a secccomp annotation of docker/default should have empty security opt." )
}
func TestSeccompContainerAnnotationTrumpsPod ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.10.1" , "1.22" )
pod := makePod ( "foo2" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar2" } ,
} ,
} )
pod . Annotations = map [ string ] string {
v1 . SeccompPodAnnotationKey : "unconfined" ,
v1 . SeccompContainerAnnotationKeyPrefix + "bar2" : "docker/default" ,
}
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo2_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar2\\.[a-f0-9]+_foo2_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . NotContains ( t , newContainer . HostConfig . SecurityOpt , "seccomp:unconfined" , "Container annotation should trump the pod annotation for seccomp." )
}
func TestSecurityOptsAreNilWithDockerV19 ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.9.1" , "1.21" )
pod := makePod ( "foo" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar" } ,
} ,
} )
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . NotContains ( t , newContainer . HostConfig . SecurityOpt , "seccomp:unconfined" , "Pods with Docker versions < 1.10 must not have seccomp disabled by default" )
}
func TestCreateAppArmorContanier ( t * testing . T ) {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.11.1" , "1.23" )
// We want to capture events.
recorder := record . NewFakeRecorder ( 20 )
dm . recorder = recorder
pod := & v1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-12-23 02:37:10 +00:00
UID : "12345678" ,
Name : "foo" ,
Namespace : "new" ,
Annotations : map [ string ] string {
apparmor . ContainerAnnotationKeyPrefix + "test" : apparmor . ProfileNamePrefix + "test-profile" ,
} ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "test" } ,
} ,
} ,
}
runSyncPod ( t , dm , fakeDocker , pod , nil , false )
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_test\\.[a-f0-9]+_foo_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
// Verify security opts.
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
securityOpts := newContainer . HostConfig . SecurityOpt
assert . Contains ( t , securityOpts , "apparmor=test-profile" , "Container should have apparmor security opt" )
cid := utilstrings . ShortenString ( fakeDocker . Created [ 1 ] , 12 )
assert . NoError ( t , expectEvent ( recorder , v1 . EventTypeNormal , events . CreatedContainer ,
fmt . Sprintf ( "Created container with docker id %s; Security:[seccomp=unconfined apparmor=test-profile]" , cid ) ) )
}
func TestSeccompLocalhostProfileIsLoaded ( t * testing . T ) {
tests := [ ] struct {
annotations map [ string ] string
expectedSecOpt string
expectedSecMsg string
expectedError string
} {
{
annotations : map [ string ] string {
v1 . SeccompPodAnnotationKey : "localhost/test" ,
} ,
expectedSecOpt : ` seccomp= { "foo":"bar"} ` ,
expectedSecMsg : "seccomp=test(md5:21aeae45053385adebd25311f9dd9cb1)" ,
} ,
{
annotations : map [ string ] string {
v1 . SeccompPodAnnotationKey : "localhost/sub/subtest" ,
} ,
expectedSecOpt : ` seccomp= { "abc":"def"} ` ,
expectedSecMsg : "seccomp=sub/subtest(md5:07c9bcb4db631f7ca191d6e0bca49f76)" ,
} ,
{
annotations : map [ string ] string {
v1 . SeccompPodAnnotationKey : "localhost/not-existing" ,
} ,
expectedError : "cannot load seccomp profile" ,
} ,
}
for i , test := range tests {
dm , fakeDocker := newTestDockerManagerWithVersion ( "1.11.0" , "1.23" )
// We want to capture events.
recorder := record . NewFakeRecorder ( 20 )
dm . recorder = recorder
dm . seccompProfileRoot = path . Join ( "fixtures" , "seccomp" )
pod := makePod ( "foo2" , & v1 . PodSpec {
Containers : [ ] v1 . Container {
{ Name : "bar2" } ,
} ,
} )
pod . Annotations = test . annotations
result := runSyncPod ( t , dm , fakeDocker , pod , nil , test . expectedError != "" )
if test . expectedError != "" {
assert . Contains ( t , result . Error ( ) . Error ( ) , test . expectedError )
continue
}
verifyCalls ( t , fakeDocker , [ ] string {
// Create pod infra container.
"create" , "start" , "inspect_container" , "inspect_container" ,
// Create container.
"create" , "start" , "inspect_container" ,
} )
fakeDocker . Lock ( )
if len ( fakeDocker . Created ) != 2 ||
! matchString ( t , "/k8s_POD\\.[a-f0-9]+_foo2_new_" , fakeDocker . Created [ 0 ] ) ||
! matchString ( t , "/k8s_bar2\\.[a-f0-9]+_foo2_new_" , fakeDocker . Created [ 1 ] ) {
t . Errorf ( "unexpected containers created %v" , fakeDocker . Created )
}
fakeDocker . Unlock ( )
newContainer , err := fakeDocker . InspectContainer ( fakeDocker . Created [ 1 ] )
if err != nil {
t . Fatalf ( "unexpected error %v" , err )
}
assert . Contains ( t , newContainer . HostConfig . SecurityOpt , test . expectedSecOpt , "The compacted seccomp json profile should be loaded." )
cid := utilstrings . ShortenString ( fakeDocker . Created [ 1 ] , 12 )
assert . NoError ( t , expectEvent ( recorder , v1 . EventTypeNormal , events . CreatedContainer ,
fmt . Sprintf ( "Created container with docker id %s; Security:[%s]" , cid , test . expectedSecMsg ) ) ,
"testcase %d" , i )
}
}
func TestGetPodStatusFromNetworkPlugin ( t * testing . T ) {
cases := [ ] struct {
pod * v1 . Pod
fakePodIP string
containerID string
infraContainerID string
networkStatusError error
expectRunning bool
expectUnknown bool
} {
{
pod : & v1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-12-23 02:37:10 +00:00
UID : "12345678" ,
Name : "foo" ,
Namespace : "new" ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container { { Name : "container" } } ,
} ,
} ,
fakePodIP : "10.10.10.10" ,
containerID : "123" ,
infraContainerID : "9876" ,
networkStatusError : nil ,
expectRunning : true ,
expectUnknown : false ,
} ,
{
pod : & v1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-12-23 02:37:10 +00:00
UID : "12345678" ,
Name : "foo" ,
Namespace : "new" ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container { { Name : "container" } } ,
} ,
} ,
fakePodIP : "" ,
containerID : "123" ,
infraContainerID : "9876" ,
networkStatusError : fmt . Errorf ( "CNI plugin error" ) ,
expectRunning : false ,
expectUnknown : true ,
} ,
}
for _ , test := range cases {
dm , fakeDocker := newTestDockerManager ( )
ctrl := gomock . NewController ( t )
fnp := mock_network . NewMockNetworkPlugin ( ctrl )
dm . networkPlugin = fnp
fakeDocker . SetFakeRunningContainers ( [ ] * FakeContainer {
{
ID : test . containerID ,
Name : fmt . Sprintf ( "/k8s_container_%s_%s_%s_42" , test . pod . Name , test . pod . Namespace , test . pod . UID ) ,
Running : true ,
} ,
{
ID : test . infraContainerID ,
Name : fmt . Sprintf ( "/k8s_POD.%s_%s_%s_%s_42" , strconv . FormatUint ( generatePodInfraContainerHash ( test . pod ) , 16 ) , test . pod . Name , test . pod . Namespace , test . pod . UID ) ,
Running : true ,
} ,
} )
fnp . EXPECT ( ) . Name ( ) . Return ( "someNetworkPlugin" ) . AnyTimes ( )
var podNetworkStatus * network . PodNetworkStatus
if test . fakePodIP != "" {
podNetworkStatus = & network . PodNetworkStatus { IP : net . ParseIP ( test . fakePodIP ) }
}
fnp . EXPECT ( ) . GetPodNetworkStatus ( test . pod . Namespace , test . pod . Name , kubecontainer . DockerID ( test . infraContainerID ) . ContainerID ( ) ) . Return ( podNetworkStatus , test . networkStatusError )
podStatus , err := dm . GetPodStatus ( test . pod . UID , test . pod . Name , test . pod . Namespace )
if err != nil {
t . Fatal ( err )
}
if podStatus . IP != test . fakePodIP {
t . Errorf ( "Got wrong ip, expected %v, got %v" , test . fakePodIP , podStatus . IP )
}
expectedStatesCount := 0
var expectedState kubecontainer . ContainerState
if test . expectRunning {
expectedState = kubecontainer . ContainerStateRunning
} else if test . expectUnknown {
expectedState = kubecontainer . ContainerStateUnknown
} else {
t . Errorf ( "Some state has to be expected" )
}
for _ , containerStatus := range podStatus . ContainerStatuses {
if containerStatus . State == expectedState {
expectedStatesCount ++
}
}
if expectedStatesCount < 1 {
t . Errorf ( "Invalid count of containers with expected state" )
}
}
}