Redact k8s connection info in node list

The node list (web and cli) displays the connection port for the
node, but the k8s drivers use that to send service account
credential info to zuul.

To avoid exposing this to users if operators have chosen to make
the nodepool-launcher webserver accessible, redact the connection
port if it is not an integer.

This also affects the command-line nodepool-list in the same way.

Change-Id: I7a309f95417d47612e40d983b3a2ec6ee4d0183a
This commit is contained in:
James E. Blair 2023-12-01 10:27:06 -08:00
parent f39d2b955a
commit 9c496e2939
3 changed files with 50 additions and 1 deletions

View File

@ -142,6 +142,14 @@ def node_list(zk, node_id=None):
else:
if zk.getNodeLockContenders(node):
locked = "locked"
port = node.connection_port
try:
int(port)
except (ValueError, TypeError):
# The port field is being used to carry connection
# information which may contain credentials (e.g., k8s
# service account). Suppress it.
port = "redacted"
values = [
node.id,
node.provider,
@ -157,7 +165,7 @@ def node_list(zk, node_id=None):
node.private_ipv4,
node.az,
node.username,
node.connection_port,
port,
node.launcher,
node.allocated_to,
node.hold_job,

View File

@ -15,8 +15,10 @@
# limitations under the License.
import fixtures
import json
import logging
import time
from urllib import request
from nodepool import tests
from nodepool.zk import zookeeper as zk
@ -572,3 +574,40 @@ class TestDriverKubernetes(tests.DBTestCase):
servers = manager.listNodes()
self.assertEqual(len(servers), 1)
def test_node_list_k8s_json(self):
# TODO: generalize the k8s test infrastructure (fake client,
# etc) and move this to test_webapp
configfile = self.setup_config('kubernetes.yaml')
pool = self.useNodepool(configfile, watermark_sleep=1)
self.startPool(pool)
webapp = self.useWebApp(pool, port=0)
webapp.start()
port = webapp.server.socket.getsockname()[1]
req = zk.NodeRequest()
req.state = zk.REQUESTED
req.node_types.append('kubernetes-namespace')
self.zk.storeNodeRequest(req)
self.log.debug("Waiting for request %s", req.id)
req = self.waitForNodeRequest(req)
self.assertEqual(req.state, zk.FULFILLED)
req = request.Request(
"http://localhost:%s/node-list" % port)
req.add_header('Accept', 'application/json')
f = request.urlopen(req)
self.assertEqual(f.info().get('Content-Type'),
'application/json')
data = f.read()
objs = json.loads(data.decode('utf8'))
# We don't check the value of 'locked' because we may get a
# cached value returned.
self.assertDictContainsSubset({'id': '0000000000',
'ipv6': None,
'label': ['kubernetes-namespace'],
'provider': 'kubespray',
'public_ipv4': None,
'connection_port': 'redacted',
'state': 'ready'}, objs[0])

View File

@ -232,6 +232,7 @@ class TestWebApp(tests.DBTestCase):
'label': ['fake-label'],
'provider': 'fake-provider',
'public_ipv4': 'fake',
'connection_port': 22,
'state': 'ready'}, objs[0])
self.assertTrue('locked' in objs[0])
# specify valid node_id
@ -251,6 +252,7 @@ class TestWebApp(tests.DBTestCase):
'label': ['fake-label'],
'provider': 'fake-provider',
'public_ipv4': 'fake',
'connection_port': 22,
'state': 'ready'}, objs[0])
self.assertTrue('locked' in objs[0])
# node_id not found