Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .ko.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Use the Jenkins base image as it is a public image that
# reliably sets up and uses a non-root user (uid 1000).
# https://github.com/jenkinsci/docker/blob/master/Dockerfile-slim#L15
# Ideally we should use an unprivileged distroless user if
# https://github.com/GoogleContainerTools/distroless/issues/235 is implemented.
baseImageOverrides:
github.com/knative/serving/test/test_images/runtime-unprivileged: jenkins/jenkins:lts-slim
32 changes: 24 additions & 8 deletions test/conformance/conformancetest_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package conformance

import (
"encoding/json"
"fmt"
"testing"

pkgTest "github.com/knative/pkg/test"
Expand All @@ -31,18 +32,33 @@ import (
. "github.com/knative/serving/pkg/reconciler/testing"
)

// fetchRuntimeInfo creates a Service that uses the 'runtime' test image, and extracts the returned output into the
// The 'runtime-unprivileged' test image uses uid 1000.
const unprivilegedUserID = 1000

// fetchRuntimeInfoUnprivileged creates a Service that uses the 'runtime-unprivileged' test image, and extracts the returned output into the
// RuntimeInfo object.
func fetchRuntimeInfoUnprivileged(t *testing.T, clients *test.Clients, options *test.Options, opts ...ServiceOption) (*test.ResourceNames, *types.RuntimeInfo, error) {
return runtimeInfo(t, clients, &test.ResourceNames{Image: runtimeUnprivileged}, options, opts...)
}

// fetchRuntimeInfo creates a Service that uses the 'runtime' test image, and extracts the returned output into the
// RuntimeInfo object. The 'runtime' image uses uid 0.
func fetchRuntimeInfo(t *testing.T, clients *test.Clients, options *test.Options, opts ...ServiceOption) (*test.ResourceNames, *types.RuntimeInfo, error) {
names := test.ResourceNames{
Service: test.ObjectNameForTest(t),
Image: runtime,
return runtimeInfo(t, clients, &test.ResourceNames{}, options, opts...)
}

func runtimeInfo(t *testing.T, clients *test.Clients, names *test.ResourceNames, options *test.Options, opts ...ServiceOption) (*test.ResourceNames, *types.RuntimeInfo, error) {
names.Service = test.ObjectNameForTest(t)
if names.Image == "" {
names.Image = runtime
} else if names.Image != runtimeUnprivileged {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this restriction?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function won't work unless the service returns the expected runtime image response. I put it there mostly for easy debugging if an unexpected image is passed in.

I thought about adding an unprivileged bool/enum rather than using the resource names, but it felt worse.

return nil, nil, fmt.Errorf("invalid image provided: %s", names.Image)
}

defer test.TearDown(clients, names)
test.CleanupOnInterrupt(func() { test.TearDown(clients, names) })
defer test.TearDown(clients, *names)
test.CleanupOnInterrupt(func() { test.TearDown(clients, *names) })

objects, err := test.CreateRunLatestServiceReady(t, clients, &names, options, opts...)
objects, err := test.CreateRunLatestServiceReady(t, clients, names, options, opts...)
if err != nil {
return nil, nil, err
}
Expand All @@ -63,5 +79,5 @@ func fetchRuntimeInfo(t *testing.T, clients *test.Clients, options *test.Options
if err != nil {
return nil, nil, err
}
return &names, &ri, nil
return names, &ri, nil
}
42 changes: 37 additions & 5 deletions test/conformance/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ import (
corev1 "k8s.io/api/core/v1"
)

const userID = 2020
const securityContextUserID = 2020

// TestMustRunAsUser verifies that a supplied runAsUser through securityContext takes
// effect as delared by "MUST" in the runtime-contract.
// effect as declared by "MUST" in the runtime-contract.
func TestMustRunAsUser(t *testing.T) {
t.Parallel()
clients := setup(t)

runAsUser := int64(userID)
runAsUser := int64(securityContextUserID)
securityContext := &corev1.SecurityContext{
RunAsUser: &runAsUser,
}
Expand All @@ -52,13 +52,45 @@ func TestMustRunAsUser(t *testing.T) {
t.Fatal("Missing user information from runtime info.")
}

if got, want := ri.Host.User.UID, userID; got != want {
if got, want := ri.Host.User.UID, securityContextUserID; got != want {
t.Errorf("uid = %d, want: %d", got, want)
}

// We expect the effective userID to match the userID as we
// did not use setuid.
if got, want := ri.Host.User.EUID, userID; got != want {
if got, want := ri.Host.User.EUID, securityContextUserID; got != want {
t.Errorf("euid = %d, want: %d", got, want)
}
}

// TestShouldRunAsUserContainerDefault verifies that a container that sets runAsUser
// in the Dockerfile is respected when executed in Knative as declared by "SHOULD"
// in the runtime-contract.
func TestShouldRunAsUserContainerDefault(t *testing.T) {
t.Parallel()
clients := setup(t)
_, ri, err := fetchRuntimeInfoUnprivileged(t, clients, &test.Options{})

if err != nil {
t.Fatalf("Error fetching runtime info: %v", err)
}

if ri.Host == nil {
t.Fatal("Missing host information from runtime info.")
}

if ri.Host.User == nil {
t.Fatal("Missing user information from runtime info.")
}

if got, want := ri.Host.User.UID, unprivilegedUserID; got != want {
t.Errorf("uid = %d, want: %d", got, want)
}

// We expect the effective userID to match the userID as we
// did not use setuid.
if got, want := ri.Host.User.EUID, unprivilegedUserID; got != want {
t.Errorf("euid = %d, want: %d", got, want)
}

}
15 changes: 8 additions & 7 deletions test/conformance/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,18 @@ import (

// Constants for test images located in test/test_images.
const (
pizzaPlanet1 = "pizzaplanetv1"
pizzaPlanet2 = "pizzaplanetv2"
failing = "failing"
helloworld = "helloworld"
httpproxy = "httpproxy"
singleThreadedImage = "singlethreaded"
timeout = "timeout"
invalidhelloworld = "invalidhelloworld"
pizzaPlanet1 = "pizzaplanetv1"
pizzaPlanet2 = "pizzaplanetv2"
printport = "printport"
runtime = "runtime"
protocols = "protocols"
failing = "failing"
invalidhelloworld = "invalidhelloworld"
runtime = "runtime"
runtimeUnprivileged = "runtime-unprivileged"
singleThreadedImage = "singlethreaded"
timeout = "timeout"

concurrentRequests = 50
// We expect to see 100% of requests succeed for traffic sent directly to revisions.
Expand Down
43 changes: 43 additions & 0 deletions test/test_images/runtime-unprivileged/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2019 The Knative 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 main

import (
"log"
"net/http"
"os"

"github.com/knative/serving/test/test_images/runtime/handlers"
)

func main() {

// We expect PORT to be defined in a Knative environment
// and don't want to mask this failure in a test image.
port, isSet := os.LookupEnv("PORT")
if !isSet {
log.Fatal("Environment variable PORT is not set.")
}

mux := http.NewServeMux()
handlers.InitHandlers(mux)

server := &http.Server{
Addr: ":" + port,
Handler: mux,
}

log.Printf("Server starting on port %s", port)
log.Fatal(server.ListenAndServe())
}
10 changes: 10 additions & 0 deletions test/test_images/runtime-unprivileged/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: runtime-unprivileged-test-image
namespace: default
spec:
template:
spec:
containers:
- image: github.com/knative/serving/test/test_images/runtime-unprivileged
3 changes: 1 addition & 2 deletions test/test_images/runtime/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ limitations under the License.
package main

import (
"fmt"
"log"
"net/http"
"os"
Expand All @@ -35,7 +34,7 @@ func main() {
handlers.InitHandlers(mux)

server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Addr: ":" + port,
Handler: mux,
}

Expand Down
5 changes: 4 additions & 1 deletion test/upload-test-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ set -o errexit

function upload_test_images() {
echo ">> Publishing test images"
local image_dir="$(dirname $0)/test_images"
# Script needs to be executed from the root directory
# to pickup .ko.yaml
cd "$( dirname "$0")/.."
local image_dir="test/test_images"
local docker_tag=$1
local tag_option=""
if [ -n "${docker_tag}" ]; then
Expand Down