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
32 changes: 16 additions & 16 deletions checks/docker/add_instead_of_copy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,14 @@ import rego.v1

import data.lib.docker

get_add contains output if {
add := docker.add[_]
is_unnecessary_add(add) if {
args := concat(" ", add.Value)
every s in {".tar", "http://", "https://", "git@"} {
not contains(args, s)
}

not contains(args, ".tar")
not contains(args, "http://")
not contains(args, "https://")
not contains(args, "git@")

not is_command_with_hash(add.Value, "file:")
not is_command_with_hash(add.Value, "multi:")

output := {
"args": args,
"cmd": add,
every prefix in {"file:", "multi:", "dir:"} {
not is_command_with_hash(add.Value, prefix)
}
}

Expand All @@ -46,8 +39,15 @@ is_command_with_hash(cmd, prefix) if {
cmd[1] == "in"
}

is_command_with_hash(cmd, prefix) if {
count(cmd) == 2
startswith(cmd[0], prefix)
}

deny contains res if {
output := get_add[_]
msg := sprintf("Consider using 'COPY %s' command instead of 'ADD %s'", [output.args, output.args])
res := result.new(msg, output.cmd)
some add in docker.add
is_unnecessary_add(add)
args := concat(" ", add.Value)
msg := sprintf("Consider using 'COPY %s' command instead of 'ADD %s'", [args, args])
res := result.new(msg, add)
}
153 changes: 56 additions & 97 deletions checks/docker/add_instead_of_copy_test.rego
Original file line number Diff line number Diff line change
@@ -1,101 +1,60 @@
package builtin.dockerfile.DS005
package builtin.dockerfile.DS005_test

import rego.v1

test_mixed_commands_denied if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [
{"Cmd": "add", "Value": ["/target/resources.tar.gz", "resources.jar"]},
{"Cmd": "add", "Value": ["/target/app.jar", "app.jar"]},
],
}]}

count(r) == 1
r[_].msg == "Consider using 'COPY /target/app.jar app.jar' command instead of 'ADD /target/app.jar app.jar'"
}

test_add_command_denied if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{
"Cmd": "add",
"Value": ["/target/app.jar", "app.jar"],
}],
}]}

count(r) == 1
r[_].msg == "Consider using 'COPY /target/app.jar app.jar' command instead of 'ADD /target/app.jar app.jar'"
}

test_run_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "run", "Value": ["tar -xjf /temp/package.file.tar.gz"]}],
}]}

count(r) == 0
}

test_copy_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "copy", "Value": ["test.txt", "test2.txt"]}],
}]}

count(r) == 0
}

test_add_file_colon_in_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["file:8b8864b3e02a33a579dc216fd51b28a6047bc8eeaa03045b258980fe0cf7fcb3", "in", "/xyz"]}],
}]}

count(r) == 0
}

test_add_multi_colon_in_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["multi:8b8864b3e02a33a579dc216fd51b28a6047bc8eeaa03045b258980fe0cf7fcb3", "in", "/xyz"]}],
}]}

count(r) == 0
}

test_add_tar_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["/target/resources.tar.gz", "resources.tar.gz"]}],
}]}

count(r) == 0
}

test_add_http_url_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["http://example.com/foo.txt", "bar.txt"]}],
}]}

count(r) == 0
}

test_add_https_url_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["https://example.com/foo.txt", "bar.txt"]}],
}]}

count(r) == 0
}

test_add_git_url_allowed if {
r := deny with input as {"Stages": [{
"Name": "alpine:3.13",
"Commands": [{"Cmd": "add", "Value": ["[email protected]:user/repo.git", "/usr/src/things/"]}],
}]}

count(r) == 0
import data.builtin.dockerfile.DS005 as check

test_add_command[name] if {
some name, tc in {
"mixed ADD commands and only one invalid": {
"cmds": [
{"Cmd": "add", "Value": ["/target/resources.tar.gz", "resources.jar"]},
{"Cmd": "add", "Value": ["/target/app.jar", "app.jar"]},
],
"expected": 1,
},
"single invalid ADD command": {
"cmds": [{"Cmd": "add", "Value": ["/target/app.jar", "app.jar"]}],
"expected": 1,
},
"RUN command allowed": {
"cmds": [{"Cmd": "run", "Value": ["tar -xjf /temp/package.file.tar.gz"]}],
"expected": 0,
},
"COPY command allowed": {
"cmds": [{"Cmd": "copy", "Value": ["test.txt", "test2.txt"]}],
"expected": 0,
},
"ADD with file:... in ... allowed": {
"cmds": [{"Cmd": "add", "Value": ["file:8b8864b3e02a33a579dc216fd51b28a6047bc8eeaa03045b258980fe0cf7fcb3", "in", "/xyz"]}],
"expected": 0,
},
"ADD with file:... without 'in' allowed": {
"cmds": [{"Cmd": "add", "Value": ["file:8b8864b3e02a33a579dc216fd51b28a6047bc8eeaa03045b258980fe0cf7fcb3", "/xyz"]}],
"expected": 0,
},
"ADD with multi:... in ... allowed": {
"cmds": [{"Cmd": "add", "Value": ["multi:8b8864b3e02a33a579dc216fd51b28a6047bc8eeaa03045b258980fe0cf7fcb3", "in", "/xyz"]}],
"expected": 0,
},
"ADD with .tar.gz allowed": {
"cmds": [{"Cmd": "add", "Value": ["/target/resources.tar.gz", "resources.tar.gz"]}],
"expected": 0,
},
"ADD with http URL allowed": {
"cmds": [{"Cmd": "add", "Value": ["http://example.com/foo.txt", "bar.txt"]}],
"expected": 0,
},
"ADD with https URL allowed": {
"cmds": [{"Cmd": "add", "Value": ["https://example.com/foo.txt", "bar.txt"]}],
"expected": 0,
},
"ADD with git@ URL allowed": {
"cmds": [{"Cmd": "add", "Value": ["[email protected]:user/repo.git", "/usr/src/things/"]}],
"expected": 0,
},
}

r := check.deny with input as {"Stages": [{"Name": "alpine:3.13", "Commands": tc.cmds}]}
count(r) == tc.expected
}