1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
summary: Verify that snap-interfaces-requests-control grants read-access to relevant endpoints
details: |
The snap-interfaces-requests-control interface grants access to prompting clients
(e.g. prompting-client) to a subset of snapd's API endpoints needed
to view and manage request prompts and request rules.
Specifically:
- /v2/notices: to read interfaces-requests-prompt and interfaces-requests-rule-update notices
- /v2/interfaces/requests/prompts: to receive and reply to request prompts
- /v2/interfaces/requests/rules: to view and manage request rules
- /v2/system-info: to check whether prompting is supported/enabled
- /v2/snaps/{name}: to get details about installed snaps
systems: [ubuntu-2*]
environment:
# not all terminals support UTF-8, but Python tries to be smart and attempts
# to guess the encoding as if the output would go to the terminal, but in
# fact all the test does is pipe the output to (go)jq
PYTHONIOENCODING: utf-8
prepare: |
if os.query is-ubuntu 20.04; then
tests.exec skip-test "Ubuntu 20.04 kernel doesn't support prompting" && exit 0
fi
if os.query is-ubuntu 22.04 && ! [[ "$SPREAD_BACKEND" =~ google ]]; then
tests.exec skip-test "Ubuntu 22.04 kernel doesn't support prompting (except on google)" && exit 0
fi
if os.query is-ubuntu 22.04 && not tests.info is-reexec-in-use; then
tests.exec skip-test "Ubuntu 22.04 AppArmor parser doesn't support prompting" && exit 0
fi
snap remove --purge api-client || true
# prerequisite for having a prompts handler service
snap set system experimental.user-daemons=true
debug: |
tests.exec is-skipped && exit 0
echo "Check kernel version"
uname -a
echo "Check kernel notification socket presence"
if ls /sys/kernel/security/apparmor/.notify ; then
echo "kernel notification socket exists"
else
echo "kernel notification socket does not exist"
fi
echo "Check system info"
snap debug api /v2/system-info
execute: |
tests.exec is-skipped && exit 0
"$TESTSTOOLS"/snaps-state install-local api-client
echo "The snap-interfaces-requests-control plug on the api-client snap is initially disconnected"
snap connections api-client | MATCH "snap-interfaces-requests-control +api-client:snap-interfaces-requests-control +- +-"
echo "Connect the snap-interfaces-requests-control plug"
snap connect api-client:snap-interfaces-requests-control
echo "Check snap can access interfaces-requests-prompt and interfaces-requests-rule-update notices under /v2/notices"
api-client --socket /run/snapd-snap.socket "/v2/notices?types=interfaces-requests-prompt" | \
gojq '."status-code"' | MATCH '^200$'
api-client --socket /run/snapd-snap.socket "/v2/notices?types=interfaces-requests-rule-update" | \
gojq '."status-code"' | MATCH '^200$'
api-client --socket /run/snapd-snap.socket "/v2/notices" | gojq '."status-code"' | MATCH '^200$'
echo "But not other notice types"
api-client --socket /run/snapd-snap.socket "/v2/notices?types=change-update,warning" | \
gojq '."status-code"' | MATCH '^403$'
echo "Check snap can access system info via /v2/system-info"
api-client --socket /run/snapd-snap.socket "/v2/system-info" | gojq '."status-code"' | MATCH '^200$'
SNAP_NAME="snapd"
if os.query is-core16; then
SNAP_NAME="core"
fi
echo "Check snap can access snap info via /v2/snaps/{name}"
api-client --socket /run/snapd-snap.socket "/v2/snaps/$SNAP_NAME" | gojq '."status-code"' | MATCH '^200$'
echo "Enable AppArmor prompting experimental feature"
snap set system experimental.apparmor-prompting=true
echo 'Check "apparmor-prompting" is shown as enabled in /v2/system-info'
api-client --socket /run/snapd-snap.socket "/v2/system-info" | \
gojq '."result"."features"."apparmor-prompting"."enabled"' | MATCH '^true$'
echo "Check snap can access prompts via /v2/interfaces/requests/prompts"
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/prompts" | \
gojq '."status-code"' | MATCH '^200$'
echo "Check snap can access a single prompt via /v2/interfaces/requests/prompts/<ID>"
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/prompts/1234123412341234" | \
gojq '."status-code"' | MATCH '^404$' # nonexistent prompt ID
# TODO: include the "home" interface and create a request prompt by attempting to list contents of $HOME
# PROMPT_ID=FIXME
# api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/prompts/$PROMPT_ID" | \
# gojq '."status-code"' | MATCH '^200$'
# echo "Check snap can reply to a prompt via /v2/interfaces/requests/prompts/<ID>
# TODO: split this line more
# api-client --socket /run/snapd-snap.socket --method=POST '{"action":"allow","lifespan":"forever","constraints":{"path-pattern":"/**","permissions":["read"]}}' "/v2/interfaces/requests/prompts/$PROMPT_ID" | \
# gojq '."status-code"' | MATCH '^200$'
# TODO: check that thread which triggered request completed successfully
echo "Check snap can access rules via /v2/interfaces/requests/rules"
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/rules" | \
gojq '."status-code"' | MATCH '^200$'
# XXX: creating rules requires polkit authentication, so for now, use snap debug api instead of api-client
# echo "Check snap can create rule via /v2/interfaces/requests/rules"
# api-client --socket /run/snapd-snap.socket --method=POST '{"action":"add","rule":{"snap":"api-client","interface":"home","constraints":{"path-pattern":"/path/to/file","permissions":{"read":{"outcome":"allow","lifespan":"forever"},"write":{"outcome":"deny","lifespan":"forever"},"execute":{"outcome":"allow","lifespan":"timespan","duration":"1h"}}}}}' "/v2/interfaces/requests/rules" > result.json
echo '{
"action": "add",
"rule": {
"snap": "api-client",
"interface": "home",
"constraints": {
"path-pattern": "/path/to/file",
"permissions": {
"read": {
"outcome": "allow",
"lifespan": "forever"
},
"write": {
"outcome": "deny",
"lifespan": "forever"
},
"execute": {
"outcome": "allow",
"lifespan": "timespan",
"duration": "1h"
}
}
}
}
}' | \
snap debug api -X POST -H 'Content-Type: application/json' "/v2/interfaces/requests/rules" | \
tee result.json
gojq '."status-code"' < result.json | MATCH '^200$'
RULE_ID=$(gojq '."result"."id"' < result.json | tr -d '"')
echo "Check snap can view a single rule via /v2/interfaces/requests/rules/<ID>"
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/rules/$RULE_ID" | \
gojq '."status-code"' | MATCH '^200$'
# XXX: modifying rules requires polkit authentication
# echo "Check snap can modify a single rule via /v2/interfaces/requests/rules/<ID>"
# api-client --socket /run/snapd-snap.socket --method=POST '{"action":"remove"}' "/v2/interfaces/requests/rules/$RULE_ID" | gojq '."status-code"' | MATCH '^200$'
echo "Check that snap can access icons info via /v2/icons and gets 404 if icon is not found"
api-client --socket /run/snapd-snap.socket "/v2/icons/api-client/icon" | gojq '."status-code"' | MATCH '^404$'
echo "Without snap-interfaces-requests-control the snap cannot access those API endpoints"
snap disconnect api-client:snap-interfaces-requests-control
# XXX: this relies on the fact that prompting remains enabled, even though
# the prerequisite of there being a snap with snap-interfaces-requests-control
# connected and a handler service running is no longer true. Otherwise, the error
# code would be 500 instead of 403.
api-client --socket /run/snapd-snap.socket "/v2/notices?types=interfaces-requests-prompt" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/notices?types=interfaces-requests-rule-update" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/system-info" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/snaps/$SNAP_NAME" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/prompts" | \
gojq '."status-code"' | MATCH '^403$'
# Try to access an arbitrary prompt ID, should fail with 403 rather than 404
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/prompts/1234123412341234" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/rules" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/interfaces/requests/rules/$RULE_ID" | \
gojq '."status-code"' | MATCH '^403$'
api-client --socket /run/snapd-snap.socket "/v2/icons/api-client/icon" | \
gojq '."status-code"' | MATCH '^403$'
|