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

Side by Side 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 unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Swarming Authors. All rights reserved. 1 # Copyright 2015 The Swarming Authors. All rights reserved.
2 # Use of this source code is governed by the Apache v2.0 license that can be 2 # Use of this source code is governed by the Apache v2.0 license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Cloud Endpoints API for configs. 5 """Cloud Endpoints API for configs.
6 6
7 * Reads/writes config service location. 7 * Reads/writes config service location.
8 * Validates configs. TODO(nodir): implement. 8 * Validates configs.
9 * Provides service metadata.
9 """ 10 """
10 11
11 import logging 12 import logging
12 13
13 from components import auth
14 from protorpc import messages 14 from protorpc import messages
15 from protorpc import message_types 15 from protorpc import message_types
16 from protorpc import remote 16 from protorpc import remote
17 import endpoints
18
19 from components import auth
17 20
18 from . import common 21 from . import common
19 from . import validation 22 from . import validation
20 23
21 24
25 METADATA_FORMAT_VERSION = "1.0"
26
27
28 def get_default_rule_set():
29 return validation.DEFAULT_RULE_SET
30
31
22 class ConfigSettingsMessage(messages.Message): 32 class ConfigSettingsMessage(messages.Message):
23 """Configuration service location.""" 33 """Configuration service location. Resembles common.ConfigSettings"""
24 # Example: 'luci-config.appspot.com' 34 # Example: 'luci-config.appspot.com'
25 service_hostname = messages.StringField(1) 35 service_hostname = messages.StringField(1)
36 # Example: 'user:luci-config@appspot.gserviceaccount.com'
37 trusted_config_account = messages.StringField(2)
26 38
27 39
28 class ValidateRequestMessage(messages.Message): 40 class ValidateRequestMessage(messages.Message):
29 config_set = messages.StringField(1, required=True) 41 config_set = messages.StringField(1, required=True)
30 path = messages.StringField(2, required=True) 42 path = messages.StringField(2, required=True)
31 content = messages.BytesField(3, required=True) 43 content = messages.BytesField(3, required=True)
32 44
33 45
34 class ValidationMessage(messages.Message): 46 class ValidationMessage(messages.Message):
35 class Severity(messages.Enum): 47 class Severity(messages.Enum):
36 DEBUG = logging.DEBUG 48 DEBUG = logging.DEBUG
37 INFO = logging.INFO 49 INFO = logging.INFO
38 WARNING = logging.WARNING 50 WARNING = logging.WARNING
39 ERROR = logging.ERROR 51 ERROR = logging.ERROR
40 CRITICAL = logging.CRITICAL 52 CRITICAL = logging.CRITICAL
41 text = messages.StringField(1, required=True) 53 text = messages.StringField(1, required=True)
42 severity = messages.EnumField(Severity, 2, required=True) 54 severity = messages.EnumField(Severity, 2, required=True)
43 55
44 56
45 class ValidateResponseMessage(messages.Message): 57 class ValidateResponseMessage(messages.Message):
46 messages = messages.MessageField(ValidationMessage, 1, repeated=True) 58 messages = messages.MessageField(ValidationMessage, 1, repeated=True)
47 59
48 60
61 def is_trusted_requester():
62 """Returns True if the requester can see the service metadata.
63
64 Used in metadata endpoint.
65
66 Returns:
67 True if the current identity is an admin or the config service.
68 """
69 if auth.is_admin():
70 return True
71
72 settings = common.ConfigSettings.cached()
73 if settings and settings.trusted_config_account:
74 identity = auth.get_current_identity()
75 if identity == settings.trusted_config_account:
76 return True
77
78 return False
79
80
81 class ConfigPattern(messages.Message):
82 """A pattern for one config file. See ServiceDynamicMetadata."""
83 config_set = messages.StringField(1, required=True)
84 path = messages.StringField(2, required=True)
85
86
87 class ServiceDynamicMetadata(messages.Message):
88 """Equivalent of config_service's ServiceDynamicMetadata proto message.
89
90 Keep this class in sync with:
91 * ServiceDynamicMetadata message in
92 appengine/config_service/proto/service_config.proto
93 * validation.validate_service_dynamic_metadata_blob()
94 * services._dict_to_dynamic_metadata()
95 """
96
97 class Validator(messages.Message):
98 patterns = messages.MessageField(ConfigPattern, 1, repeated=True)
99 url = messages.StringField(2, required=True)
100
101 version = messages.StringField(1, required=True)
102 validation = messages.MessageField(Validator, 2)
103
104
49 @auth.endpoints_api(name='config', version='v1', title='Configuration service') 105 @auth.endpoints_api(name='config', version='v1', title='Configuration service')
50 class ConfigApi(remote.Service): 106 class ConfigApi(remote.Service):
51 """Configuration service.""" 107 """Configuration service."""
52 108
53 @auth.endpoints_method( 109 @auth.endpoints_method(
54 ConfigSettingsMessage, ConfigSettingsMessage, 110 ConfigSettingsMessage, ConfigSettingsMessage,
55 http_method='POST') 111 http_method='POST')
56 @auth.require(auth.is_admin) 112 @auth.require(auth.is_admin)
57 def settings(self, request): 113 def settings(self, request):
58 """Reads/writes config service location. Accessible only by admins.""" 114 """Reads/writes config service location. Accessible only by admins."""
59 settings = common.ConfigSettings.fetch() or common.ConfigSettings() 115 settings = common.ConfigSettings.fetch() or common.ConfigSettings()
116 delta = {}
60 if request.service_hostname is not None: 117 if request.service_hostname is not None:
61 # Change only if service_hostname was specified. 118 delta['service_hostname'] = request.service_hostname
62 changed = settings.modify(service_hostname=request.service_hostname) 119 if request.trusted_config_account is not None:
63 if changed: 120 try:
64 logging.warning('Updated config settings') 121 delta['trusted_config_account'] = auth.Identity.from_bytes(
122 request.trusted_config_account)
123 except ValueError as ex:
124 raise endpoints.BadRequestException(
125 'Invalid trusted_config_account %s: %s' % (
126 request.trusted_config_account,
127 ex.message))
128 changed = settings.modify(**delta)
129 if changed:
130 logging.warning('Updated config settings')
65 settings = common.ConfigSettings.fetch() or settings 131 settings = common.ConfigSettings.fetch() or settings
66 return ConfigSettingsMessage( 132 return ConfigSettingsMessage(
67 service_hostname=settings.service_hostname, 133 service_hostname=settings.service_hostname,
134 trusted_config_account=(
135 settings.trusted_config_account.to_bytes()
136 if settings.trusted_config_account else None)
68 ) 137 )
69 138
70 @auth.endpoints_method( 139 @auth.endpoints_method(
71 ValidateRequestMessage, ValidateResponseMessage, http_method='POST') 140 ValidateRequestMessage, ValidateResponseMessage, http_method='POST')
72 def validate(self, request): 141 def validate(self, request):
73 """Validates a config. 142 """Validates a config.
74 143
75 Compatible with validation protocol described in ValidationCfg message of 144 Compatible with validation protocol described in ValidationCfg message of
76 /appengine/config_service/proto/service_config.proto. 145 /appengine/config_service/proto/service_config.proto.
77 """ 146 """
78 ctx = validation.Context() 147 ctx = validation.Context()
79 validation.validate(request.config_set, request.path, request.content, ctx) 148 validation.validate(request.config_set, request.path, request.content, ctx)
80 149
81 res = ValidateResponseMessage() 150 res = ValidateResponseMessage()
82 for m in ctx.result().messages: 151 for m in ctx.result().messages:
83 res.messages.append(ValidationMessage( 152 res.messages.append(ValidationMessage(
84 severity=ValidationMessage.Severity.lookup_by_number(m.severity), 153 severity=ValidationMessage.Severity.lookup_by_number(m.severity),
85 text=m.text, 154 text=m.text,
86 )) 155 ))
87 return res 156 return res
157
158 @auth.endpoints_method(
159 message_types.VoidMessage, ServiceDynamicMetadata, path='metadata')
160 @auth.require(is_trusted_requester)
161 def get_metadata(self, _request):
162 """Describes a service. Used by config service to discover other services.
163 """
164 meta = ServiceDynamicMetadata(version=METADATA_FORMAT_VERSION)
165 http_headers = dict(self.request_state.headers)
166 assert 'host' in http_headers, http_headers
167 meta.validation = meta.Validator(
168 url='https://{hostname}/_ah/api/{name}/{version}/{path}validate'.format(
169 hostname=http_headers['host'],
170 name=self.api_info.name,
171 version=self.api_info.version,
172 path=self.api_info.path or '',
173 )
174 )
175 for p in sorted(get_default_rule_set().patterns()):
176 meta.validation.patterns.append(ConfigPattern(**p._asdict()))
177 return meta
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698