blob: 354b49b32183e54216043e70fd4898f7114e5ea3 [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "soma/spec_reader.h"
#include <memory>
#include <string>
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include <gtest/gtest.h>
#include "soma/container_spec_wrapper.h"
#include "soma/namespace.h"
#include "soma/port.h"
#include "soma/service_name.h"
namespace soma {
namespace parser {
class ContainerSpecReaderTest : public ::testing::Test {
public:
ContainerSpecReaderTest() {}
virtual ~ContainerSpecReaderTest() {}
void SetUp() override {
ASSERT_TRUE(tmpdir_.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateTemporaryFileInDir(tmpdir_.path(), &scratch_));
}
protected:
std::unique_ptr<base::DictionaryValue> BuildBaselineValue() {
return BuildBaselineWithCL(true);
}
std::unique_ptr<base::DictionaryValue> BuildBaselineValueNoCL() {
return BuildBaselineWithCL(false);
}
void WriteValue(base::Value* to_write, const base::FilePath& file) {
std::string value_string;
ASSERT_TRUE(base::JSONWriter::Write(to_write, &value_string));
ASSERT_EQ(
base::WriteFile(file, value_string.c_str(), value_string.length()),
value_string.length());
}
void CheckSpecBaseline(ContainerSpecWrapper* spec) {
ASSERT_TRUE(spec);
ASSERT_EQ(scratch_.value(), spec->name());
ASSERT_EQ(base::UintToString(spec->uid()), kUid);
ASSERT_EQ(base::UintToString(spec->gid()), kGid);
ASSERT_PRED2(
[](const std::string& a, const std::string& b) {
return EndsWith(a, b, false /* case insensitive */);
},
spec->service_bundle_path().value(), kServiceBundleName);
}
ContainerSpecReader reader_;
base::ScopedTempDir tmpdir_;
base::FilePath scratch_;
private:
static const char kServiceBundleName[];
static const char kUid[];
static const char kGid[];
std::unique_ptr<base::DictionaryValue> BuildBaselineWithCL(bool with_cl) {
std::unique_ptr<base::DictionaryValue> app_dict(new base::DictionaryValue);
app_dict->SetString(ContainerSpecReader::kServiceBundleNameKey,
kServiceBundleName);
app_dict->SetString(ContainerSpecReader::kUidKey, kUid);
app_dict->SetString(ContainerSpecReader::kGidKey, kGid);
if (with_cl) {
std::unique_ptr<base::ListValue> cl(new base::ListValue);
cl->AppendString("foo");
app_dict->Set(ContainerSpecReader::kCommandLineKey, cl.release());
}
std::unique_ptr<base::ListValue> apps_list(new base::ListValue);
apps_list->Append(app_dict.release());
std::unique_ptr<base::DictionaryValue> baseline(new base::DictionaryValue);
baseline->Set(ContainerSpecReader::kAppsKey, apps_list.release());
return std::move(baseline);
}
};
const char ContainerSpecReaderTest::kServiceBundleName[] = "bundle";
const char ContainerSpecReaderTest::kUid[] = "1";
const char ContainerSpecReaderTest::kGid[] = "2";
TEST_F(ContainerSpecReaderTest, BaselineSpec) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
CheckSpecBaseline(spec.get());
}
TEST_F(ContainerSpecReaderTest, EmptyCommandLine) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValueNoCL();
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
EXPECT_EQ(spec.get(), nullptr);
}
namespace {
std::unique_ptr<base::DictionaryValue> CreateAnnotation(
const std::string& name, const std::string& value) {
std::unique_ptr<base::DictionaryValue> annotation(new base::DictionaryValue);
annotation->SetString("name", name);
annotation->SetString("value", value);
return std::move(annotation);
}
} // namespace
TEST_F(ContainerSpecReaderTest, OneServiceName) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
std::unique_ptr<base::ListValue> annotations(new base::ListValue);
annotations->Append(CreateAnnotation("service-0", "foo").release());
baseline->Set(parser::service_name::kListKey, annotations.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
EXPECT_TRUE(spec->ProvidesServiceNamed("foo"));
}
TEST_F(ContainerSpecReaderTest, SkipBogusServiceName) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
std::unique_ptr<base::ListValue> annotations(new base::ListValue);
annotations->Append(CreateAnnotation("service-0", "foo").release());
annotations->Append(CreateAnnotation("bugagoo", "bar").release());
annotations->Append(CreateAnnotation("service-1", "baz").release());
baseline->Set(parser::service_name::kListKey, annotations.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
EXPECT_TRUE(spec->ProvidesServiceNamed("foo"));
EXPECT_TRUE(spec->ProvidesServiceNamed("baz"));
EXPECT_FALSE(spec->ProvidesServiceNamed("bar"));
}
namespace {
std::unique_ptr<base::DictionaryValue> CreatePort(const std::string& protocol,
const parser::port::Number port) {
std::unique_ptr<base::DictionaryValue> port_dict(new base::DictionaryValue);
port_dict->SetString(parser::port::kProtocolKey, protocol);
port_dict->SetInteger(parser::port::kPortKey, port);
return std::move(port_dict);
}
} // namespace
TEST_F(ContainerSpecReaderTest, SpecWithListenPorts) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
const parser::port::Number port1 = 80;
const parser::port::Number port2 = 9222;
const parser::port::Number invalid_port = -8;
std::unique_ptr<base::ListValue> listen_ports(new base::ListValue);
listen_ports->Append(CreatePort(parser::port::kTcpProtocol, port1).release());
listen_ports->Append(CreatePort(parser::port::kTcpProtocol, port2).release());
listen_ports->Append(CreatePort(parser::port::kUdpProtocol, port1).release());
baseline->Set(parser::port::kListKey, listen_ports.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
CheckSpecBaseline(spec.get());
EXPECT_TRUE(spec->TcpListenPortIsAllowed(port1));
EXPECT_TRUE(spec->TcpListenPortIsAllowed(port2));
EXPECT_TRUE(spec->UdpListenPortIsAllowed(port1));
EXPECT_FALSE(spec->UdpListenPortIsAllowed(81));
std::unique_ptr<base::ListValue> listen_ports_invalid(new base::ListValue);
listen_ports_invalid->Append(
CreatePort(parser::port::kUdpProtocol, invalid_port).release());
baseline->Set(parser::port::kListKey, listen_ports_invalid.release());
WriteValue(baseline.get(), scratch_);
spec = reader_.Read(scratch_);
EXPECT_EQ(spec.get(), nullptr);
}
TEST_F(ContainerSpecReaderTest, SpecWithWildcardPort) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
std::unique_ptr<base::ListValue> listen_ports(new base::ListValue);
listen_ports->Append(CreatePort(parser::port::kTcpProtocol,
parser::port::kWildcard).release());
baseline->Set(parser::port::kListKey, listen_ports.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
CheckSpecBaseline(spec.get());
EXPECT_TRUE(spec->TcpListenPortIsAllowed(80));
EXPECT_TRUE(spec->TcpListenPortIsAllowed(90));
EXPECT_FALSE(spec->UdpListenPortIsAllowed(90));
}
namespace {
std::unique_ptr<base::ListValue> ListFromPair(const std::pair<int, int>& pair) {
std::unique_ptr<base::ListValue> list(new base::ListValue);
list->AppendInteger(pair.first);
list->AppendInteger(pair.second);
return std::move(list);
}
} // anonymous namespace
TEST_F(ContainerSpecReaderTest, SpecWithDeviceFilters) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
const char kPathFilter1[] = "/dev/d1";
const char kPathFilter2[] = "/dev/d2";
std::unique_ptr<base::ListValue> device_path_filters(new base::ListValue);
device_path_filters->AppendString(kPathFilter1);
device_path_filters->AppendString(kPathFilter2);
baseline->Set(DevicePathFilter::kListKey, device_path_filters.release());
std::unique_ptr<base::ListValue> device_node_filters(new base::ListValue);
device_node_filters->Append(ListFromPair({8, 0}).release());
device_node_filters->Append(ListFromPair({4, -1}).release());
baseline->Set(DeviceNodeFilter::kListKey, device_node_filters.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
CheckSpecBaseline(spec.get());
EXPECT_TRUE(spec->DevicePathIsAllowed(base::FilePath(kPathFilter1)));
EXPECT_TRUE(spec->DevicePathIsAllowed(base::FilePath(kPathFilter2)));
EXPECT_TRUE(spec->DeviceNodeIsAllowed(8, 0));
EXPECT_TRUE(spec->DeviceNodeIsAllowed(4, -1));
}
TEST_F(ContainerSpecReaderTest, SpecWithNamespaces) {
std::unique_ptr<base::DictionaryValue> baseline = BuildBaselineValue();
std::unique_ptr<base::ListValue> namespaces(new base::ListValue);
namespaces->AppendString(ns::kNewIpc);
namespaces->AppendString(ns::kNewPid);
baseline->Set(ns::kListKey, namespaces.release());
WriteValue(baseline.get(), scratch_);
std::unique_ptr<ContainerSpecWrapper> spec = reader_.Read(scratch_);
CheckSpecBaseline(spec.get());
EXPECT_TRUE(spec->ShouldApplyNamespace(ContainerSpec::NEWIPC));
EXPECT_TRUE(spec->ShouldApplyNamespace(ContainerSpec::NEWPID));
EXPECT_FALSE(spec->ShouldApplyNamespace(ContainerSpec::NEWNS));
}
} // namespace parser
} // namespace soma