Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Unified Diff: appengine/components/components/config/endpoint.py

Issue 1224913002: luci-config: fine-grained acls (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: fine-grained acls for service configs Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine/components/components/config/endpoint.py
diff --git a/appengine/components/components/config/endpoint.py b/appengine/components/components/config/endpoint.py
index e7c9e4ebd31b0cb7f941c0b8c8716993b10c5df8..c48434ef1280cad892992f624a2fe8d9a35c0ba5 100644
--- a/appengine/components/components/config/endpoint.py
+++ b/appengine/components/components/config/endpoint.py
@@ -5,24 +5,36 @@
"""Cloud Endpoints API for configs.
* Reads/writes config service location.
-* Validates configs. TODO(nodir): implement.
+* Validates configs.
+* Provides service metadata.
"""
import logging
-from components import auth
from protorpc import messages
from protorpc import message_types
from protorpc import remote
+import endpoints
+
+from components import auth
from . import common
from . import validation
+METADATA_FORMAT_VERSION = "1.0"
+
+
+def get_default_rule_set():
+ return validation.DEFAULT_RULE_SET
+
+
class ConfigSettingsMessage(messages.Message):
- """Configuration service location."""
+ """Configuration service location. Resembles common.ConfigSettings"""
# Example: 'luci-config.appspot.com'
service_hostname = messages.StringField(1)
+ # Example: 'user:luci-config@appspot.gserviceaccount.com'
+ trusted_config_account = messages.StringField(2)
class ValidateRequestMessage(messages.Message):
@@ -46,6 +58,50 @@ class ValidateResponseMessage(messages.Message):
messages = messages.MessageField(ValidationMessage, 1, repeated=True)
+def is_trusted_requester():
+ """Returns True if the requester can see the service metadata.
+
+ Used in metadata endpoint.
+
+ Returns:
+ True if the current identity is an admin or the config service.
+ """
+ if auth.is_admin():
+ return True
+
+ settings = common.ConfigSettings.cached()
+ if settings and settings.trusted_config_account:
+ identity = auth.get_current_identity()
+ if identity == settings.trusted_config_account:
+ return True
+
+ return False
+
+
+class ConfigPattern(messages.Message):
+ """A pattern for one config file. See ServiceDynamicMetadata."""
+ config_set = messages.StringField(1, required=True)
+ path = messages.StringField(2, required=True)
+
+
+class ServiceDynamicMetadata(messages.Message):
+ """Equivalent of config_service's ServiceDynamicMetadata proto message.
+
+ Keep this class in sync with:
+ * ServiceDynamicMetadata message in
+ appengine/config_service/proto/service_config.proto
+ * validation.validate_service_dynamic_metadata_blob()
+ * services._dict_to_dynamic_metadata()
+ """
+
+ class Validator(messages.Message):
+ patterns = messages.MessageField(ConfigPattern, 1, repeated=True)
+ url = messages.StringField(2, required=True)
+
+ version = messages.StringField(1, required=True)
+ validation = messages.MessageField(Validator, 2)
+
+
@auth.endpoints_api(name='config', version='v1', title='Configuration service')
class ConfigApi(remote.Service):
"""Configuration service."""
@@ -57,14 +113,27 @@ class ConfigApi(remote.Service):
def settings(self, request):
"""Reads/writes config service location. Accessible only by admins."""
settings = common.ConfigSettings.fetch() or common.ConfigSettings()
+ delta = {}
if request.service_hostname is not None:
- # Change only if service_hostname was specified.
- changed = settings.modify(service_hostname=request.service_hostname)
- if changed:
- logging.warning('Updated config settings')
+ delta['service_hostname'] = request.service_hostname
+ if request.trusted_config_account is not None:
+ try:
+ delta['trusted_config_account'] = auth.Identity.from_bytes(
+ request.trusted_config_account)
+ except ValueError as ex:
+ raise endpoints.BadRequestException(
+ 'Invalid trusted_config_account %s: %s' % (
+ request.trusted_config_account,
+ ex.message))
+ changed = settings.modify(**delta)
+ if changed:
+ logging.warning('Updated config settings')
settings = common.ConfigSettings.fetch() or settings
return ConfigSettingsMessage(
service_hostname=settings.service_hostname,
+ trusted_config_account=(
+ settings.trusted_config_account.to_bytes()
+ if settings.trusted_config_account else None)
)
@auth.endpoints_method(
@@ -85,3 +154,24 @@ class ConfigApi(remote.Service):
text=m.text,
))
return res
+
+ @auth.endpoints_method(
+ message_types.VoidMessage, ServiceDynamicMetadata, path='metadata')
+ @auth.require(is_trusted_requester)
+ def get_metadata(self, _request):
+ """Describes a service. Used by config service to discover other services.
+ """
+ meta = ServiceDynamicMetadata(version=METADATA_FORMAT_VERSION)
+ http_headers = dict(self.request_state.headers)
+ assert 'host' in http_headers, http_headers
+ meta.validation = meta.Validator(
+ url='https://{hostname}/_ah/api/{name}/{version}/{path}validate'.format(
+ hostname=http_headers['host'],
+ name=self.api_info.name,
+ version=self.api_info.version,
+ path=self.api_info.path or '',
+ )
+ )
+ for p in sorted(get_default_rule_set().patterns()):
+ meta.validation.patterns.append(ConfigPattern(**p._asdict()))
+ return meta

Powered by Google App Engine
This is Rietveld 408576698