|
12 | 12 | using System.Net.Http;
|
13 | 13 | using GitHub.Runner.Common;
|
14 | 14 | using GitHub.Runner.Sdk;
|
| 15 | +using GitHub.DistributedTask.Pipelines.ContextData; |
15 | 16 |
|
16 | 17 | namespace GitHub.Runner.Worker
|
17 | 18 | {
|
@@ -63,6 +64,14 @@ public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message,
|
63 | 64 | jobContext.InitializeJob(message, jobRequestCancellationToken);
|
64 | 65 | Trace.Info("Starting the job execution context.");
|
65 | 66 | jobContext.Start();
|
| 67 | + var githubContext = jobContext.ExpressionValues["github"] as GitHubContext; |
| 68 | + |
| 69 | + if (!JobPassesSecurityRestrictions(jobContext)) |
| 70 | + { |
| 71 | + jobContext.Error("Running job on this worker disallowed by security policy"); |
| 72 | + return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed); |
| 73 | + } |
| 74 | + |
66 | 75 | jobContext.Debug($"Starting: {message.JobDisplayName}");
|
67 | 76 |
|
68 | 77 | runnerShutdownRegistration = HostContext.RunnerShutdownToken.Register(() =>
|
@@ -189,6 +198,81 @@ public async Task<TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message,
|
189 | 198 | }
|
190 | 199 | }
|
191 | 200 |
|
| 201 | + private bool JobPassesSecurityRestrictions(IExecutionContext jobContext) |
| 202 | + { |
| 203 | + var gitHubContext = jobContext.ExpressionValues["github"] as GitHubContext; |
| 204 | + |
| 205 | + try { |
| 206 | + if (gitHubContext.IsPullRequest()) |
| 207 | + { |
| 208 | + return OkayToRunPullRequest(gitHubContext); |
| 209 | + } |
| 210 | + |
| 211 | + return true; |
| 212 | + } |
| 213 | + catch (Exception ex) |
| 214 | + { |
| 215 | + Trace.Error("Caught exception in JobPassesSecurityRestrictions"); |
| 216 | + Trace.Error("As a safety precaution we are not allowing this job to run"); |
| 217 | + Trace.Error(ex); |
| 218 | + return false; |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + private bool OkayToRunPullRequest(GitHubContext gitHubContext) |
| 223 | + { |
| 224 | + var configStore = HostContext.GetService<IConfigurationStore>(); |
| 225 | + var settings = configStore.GetSettings(); |
| 226 | + var prSecuritySettings = settings.PullRequestSecuritySettings; |
| 227 | + |
| 228 | + if (prSecuritySettings is null) { |
| 229 | + Trace.Info("No pullRequestSecurity defined in settings, allowing this build"); |
| 230 | + return true; |
| 231 | + } |
| 232 | + |
| 233 | + var githubEvent = gitHubContext["event"] as DictionaryContextData; |
| 234 | + var prData = githubEvent["pull_request"] as DictionaryContextData; |
| 235 | + |
| 236 | + var authorAssociation = prData.TryGetValue("author_association", out var value) |
| 237 | + ? value as StringContextData : null; |
| 238 | + |
| 239 | + |
| 240 | + // TODO: Allow COLLABORATOR, MEMBER too -- possibly by a config setting |
| 241 | + if (authorAssociation == "OWNER") |
| 242 | + { |
| 243 | + Trace.Info("PR is from the repo owner, always allowed"); |
| 244 | + return true; |
| 245 | + } |
| 246 | + else if (prSecuritySettings.AllowContributors && authorAssociation == "COLLABORATOR") { |
| 247 | + Trace.Info("PR is from the repo collaborator, allowing"); |
| 248 | + return true; |
| 249 | + } |
| 250 | + |
| 251 | + var prHead = prData["head"] as DictionaryContextData; |
| 252 | + var prUser = prHead["user"] as DictionaryContextData; |
| 253 | + var prUserLogin = prUser["login"] as StringContextData; |
| 254 | + |
| 255 | + Trace.Info($"GitHub PR author is {prUserLogin as StringContextData}"); |
| 256 | + |
| 257 | + if (prUserLogin == null) |
| 258 | + { |
| 259 | + Trace.Info("Unable to get PR author, not allowing PR to run"); |
| 260 | + return false; |
| 261 | + } |
| 262 | + |
| 263 | + if (prSecuritySettings.AllowedAuthors.Contains(prUserLogin)) |
| 264 | + { |
| 265 | + Trace.Info("Author in PR allowed list"); |
| 266 | + return true; |
| 267 | + } |
| 268 | + else |
| 269 | + { |
| 270 | + Trace.Info($"Not running job as author ({prUserLogin}) is not in {{{string.Join(", ", prSecuritySettings.AllowedAuthors)}}}"); |
| 271 | + |
| 272 | + return false; |
| 273 | + } |
| 274 | + } |
| 275 | + |
192 | 276 | private async Task<TaskResult> CompleteJobAsync(IJobServer jobServer, IExecutionContext jobContext, Pipelines.AgentJobRequestMessage message, TaskResult? taskResult = null)
|
193 | 277 | {
|
194 | 278 | jobContext.Debug($"Finishing: {message.JobDisplayName}");
|
|
0 commit comments