github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/utils_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package layer
    19  
    20  import (
    21  	"archive/tar"
    22  	"testing"
    23  
    24  	rspec "github.com/opencontainers/runtime-spec/specs-go"
    25  	rootlesscontainers "github.com/rootless-containers/proto/go-proto"
    26  	"google.golang.org/protobuf/proto"
    27  )
    28  
    29  // We need to have individual tests for the rootlesscontainers-proto, because
    30  // when we do integration tests for the "user.rootlesscontainers" handling we
    31  // don't have access to the protobuf parser from within bats (we just match the
    32  // strings which isn't as easy to debug).
    33  
    34  // TestMapRootless ensures that mapHeader correctly handles the rootless case
    35  // (both for user.rootlesscontainers and the more general case).
    36  func TestMapRootless(t *testing.T) {
    37  	// Just a basic "*archive/tar.Header" that we use for testing.
    38  	rootUID := 1000
    39  	rootGID := 100
    40  	mapOptions := MapOptions{
    41  		UIDMappings: []rspec.LinuxIDMapping{{HostID: uint32(rootUID), ContainerID: 0, Size: 1}},
    42  		GIDMappings: []rspec.LinuxIDMapping{{HostID: uint32(rootGID), ContainerID: 0, Size: 1}},
    43  		Rootless:    true,
    44  	}
    45  	baseHdr := tar.Header{
    46  		Name:     "etc/passwd",
    47  		Typeflag: tar.TypeReg,
    48  		Xattrs:   make(map[string]string),
    49  	}
    50  
    51  	for idx, test := range []struct {
    52  		uid, gid       int                          // (uid, gid) for hdr
    53  		proto          *rootlesscontainers.Resource // (uid, gid) for xattr
    54  		outUID, outGID int                          // (uid, gid) after map
    55  		errorExpected  bool
    56  	}{
    57  		// Noop values.
    58  		{0, 0, nil, 0, 0, false},
    59  		{0, 0, &rootlesscontainers.Resource{Uid: rootlesscontainers.NoopID, Gid: rootlesscontainers.NoopID}, 0, 0, false},
    60  		// Basic mappings.
    61  		{0, 0, &rootlesscontainers.Resource{Uid: 123, Gid: rootlesscontainers.NoopID}, 123, 0, false},
    62  		{0, 0, &rootlesscontainers.Resource{Uid: rootlesscontainers.NoopID, Gid: 456}, 0, 456, false},
    63  		{0, 0, &rootlesscontainers.Resource{Uid: 8001, Gid: 1337}, 8001, 1337, false},
    64  		// XXX: We cannot enable these tests at the moment because we currently
    65  		//      just ignore the owner and always set it to 0 for rootless
    66  		//      containers.
    67  		//{851, 182, &rootlesscontainers.Resource{Uid: rootlesscontainers.NoopID, Gid: rootlesscontainers.NoopID}, 851, 182, false},
    68  		//{154, 992, &rootlesscontainers.Resource{Uid: 9982, Gid: rootlesscontainers.NoopID}, 9982, 992, false},
    69  		//{291, 875, &rootlesscontainers.Resource{Uid: 42158, Gid: 31337}, 42158, 31337, false},
    70  	} {
    71  		// Update baseHdr.
    72  		baseHdr.Uid = test.uid
    73  		baseHdr.Gid = test.gid
    74  		delete(baseHdr.Xattrs, rootlesscontainers.Keyname)
    75  		if test.proto != nil {
    76  			payload, err := proto.Marshal(test.proto)
    77  			if err != nil {
    78  				t.Errorf("test%d: failed to marshal proto in test", idx)
    79  				continue
    80  			}
    81  			baseHdr.Xattrs[rootlesscontainers.Keyname] = string(payload)
    82  		}
    83  
    84  		// Map.
    85  		err := mapHeader(&baseHdr, mapOptions)
    86  		if (err != nil) != test.errorExpected {
    87  			t.Errorf("test%d: error value was unexpected, errorExpected=%v got err=%v", idx, test.errorExpected, err)
    88  			continue
    89  		}
    90  
    91  		// Output header shouldn't contain "user.rootlesscontainers".
    92  		if value, ok := baseHdr.Xattrs[rootlesscontainers.Keyname]; ok {
    93  			t.Errorf("test%d: 'user.rootlesscontainers' present in output: value=%v", idx, value)
    94  		}
    95  		// Make sure that the uid and gid are what we wanted.
    96  		if uid := baseHdr.Uid; uid != test.outUID {
    97  			t.Errorf("test%d: got unexpected uid: wanted %v got %v", idx, test.outUID, uid)
    98  		}
    99  		if gid := baseHdr.Gid; gid != test.outGID {
   100  			t.Errorf("test%d: got unexpected gid: wanted %v got %v", idx, test.outGID, gid)
   101  		}
   102  	}
   103  }
   104  
   105  // TestUnmapRootless ensures that unmapHeader correctly handles the rootless
   106  // case (both for user.rootlesscontainers and the more general case).
   107  func TestUnmapRootless(t *testing.T) {
   108  	// Just a basic "*archive/tar.Header" that we use for testing.
   109  	rootUID := 1000
   110  	rootGID := 100
   111  	mapOptions := MapOptions{
   112  		UIDMappings: []rspec.LinuxIDMapping{{HostID: uint32(rootUID), ContainerID: 0, Size: 1}},
   113  		GIDMappings: []rspec.LinuxIDMapping{{HostID: uint32(rootGID), ContainerID: 0, Size: 1}},
   114  		Rootless:    true,
   115  	}
   116  	baseHdr := tar.Header{
   117  		Name:     "etc/passwd",
   118  		Typeflag: tar.TypeReg,
   119  	}
   120  
   121  	for idx, test := range []struct {
   122  		uid, gid int                          // (uid, gid) for hdr
   123  		proto    *rootlesscontainers.Resource // expected "user.rootlesscontainers" payload
   124  	}{
   125  		// Basic mappings to check.
   126  		{0, 0, nil},
   127  		{521, 0, &rootlesscontainers.Resource{Uid: 521, Gid: rootlesscontainers.NoopID}},
   128  		{0, 942, &rootlesscontainers.Resource{Uid: rootlesscontainers.NoopID, Gid: 942}},
   129  		{333, 825, &rootlesscontainers.Resource{Uid: 333, Gid: 825}},
   130  		{185, 9923, &rootlesscontainers.Resource{Uid: 185, Gid: 9923}},
   131  	} {
   132  		// Update baseHdr.
   133  		baseHdr.Uid = test.uid
   134  		baseHdr.Gid = test.gid
   135  		delete(baseHdr.Xattrs, rootlesscontainers.Keyname)
   136  
   137  		// Unmap.
   138  		if err := unmapHeader(&baseHdr, mapOptions); err != nil {
   139  			t.Errorf("test%d: unexpected error in unmapHeader: %v", idx, err)
   140  			continue
   141  		}
   142  
   143  		// Check the owner.
   144  		if baseHdr.Uid != rootUID {
   145  			t.Errorf("test%d: got hdr uid %d when expected %d", idx, baseHdr.Uid, rootUID)
   146  		}
   147  		if baseHdr.Gid != rootGID {
   148  			t.Errorf("test%d: got hdr gid %d when expected %d", idx, baseHdr.Gid, rootGID)
   149  		}
   150  
   151  		// Check that the xattr is what we wanted.
   152  		if payload, ok := baseHdr.Xattrs[rootlesscontainers.Keyname]; (test.proto != nil) != ok {
   153  			// Only bad if we expected a proto...
   154  			t.Errorf("test%d: unexpected situation: expected xattr exist to be %v", idx, test.proto != nil)
   155  			continue
   156  		} else if ok {
   157  			var parsed rootlesscontainers.Resource
   158  			if err := proto.Unmarshal([]byte(payload), &parsed); err != nil {
   159  				t.Errorf("test%d: unexpected error parsing payload: %v", idx, err)
   160  				continue
   161  			}
   162  
   163  			if parsed.Uid != test.proto.Uid {
   164  				t.Errorf("test%d: got xattr uid %d when expected %d", idx, parsed.Uid, test.proto.Uid)
   165  			}
   166  			if parsed.Gid != test.proto.Gid {
   167  				t.Errorf("test%d: got xattr gid %d when expected %d", idx, parsed.Gid, test.proto.Gid)
   168  			}
   169  		}
   170  
   171  		// Finally, just do a check to ensure that mapHeader returns the old values.
   172  		if err := mapHeader(&baseHdr, mapOptions); err != nil {
   173  			t.Errorf("test%d: unexpected error in mapHeader: %v", idx, err)
   174  			continue
   175  		}
   176  		if baseHdr.Uid != test.uid {
   177  			t.Errorf("test%d: round-trip of uid failed: expected %d got %d", idx, test.uid, baseHdr.Uid)
   178  		}
   179  		if baseHdr.Gid != test.gid {
   180  			t.Errorf("test%d: round-trip of gid failed: expected %d got %d", idx, test.gid, baseHdr.Gid)
   181  		}
   182  	}
   183  }