{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/community/pulls/comments/177281591","pull_request_review_id":107114056,"id":177281591,"diff_hunk":"@@ -0,0 +1,477 @@\n+# PodRestrictions\n+\n+## Overview\n+\n+This is a proposal for a new policy object (and associated admission controller) that adds basic\n+restrictions to most fields on a Pod. This is motivated by a desire to unify the various policies we\n+have now (which all behave in slightly different ways), and to unblock expanding the set of\n+restricted fields. Furthermore, it aims to solve some of the key usability problems with\n+PodSecurityPolicies, such as the inability to compose policies and the difficulty in binding\n+policies.\n+\n+### Existing Policies\n+\n+There are multiple policies that constrain pod objects today. PodRestriction would supersede these\n+policies, and they would eventually be deprecated.\n+\n+[AlwaysPullImages](https://kubernetes.io/docs/admin/admission-controllers/#alwayspullimages) -\n+Admission controller that sets the PullAlways ImagePullPolicy to be set on every container.\n+\n+[PodSecurityPolicy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) - Restricts\n+PodSecurityContext, SecurityContext, and some volume options. Bound to users and service accounts.\n+\n+[LimitRanger](https://kubernetes.io/docs/admin/admission-controllers/#limitranger) - Limits the\n+range of resource limits that can be placed on a Pod. Intersection of LimitRanges in a namespace.\n+\n+[Toleration](https://kubernetes.io/docs/admin/admission-controllers/#podtolerationrestriction) &\n+[NodeSelector](https://kubernetes.io/docs/admin/admission-controllers/#podnodeselector)\n+restrictions - Restrict scheduling options through namespace annotations. Several proposals are out\n+for new policies to supercede these ([#1937](https://github.com/kubernetes/community/pull/1937),\n+[#48041](https://github.com/kubernetes/kubernetes/issues/48041),\n+[#61185](https://github.com/kubernetes/kubernetes/issues/61185)).\n+\n+There have been other proposals for policies restricting fields like labels, and annotations.\n+\n+## Scope\n+\n+This proposal is about building a new policy type that can place restrictions on any or all pod\n+fields (see [Monolith vs. Modular](#monolith-vs-modular)). However, the goal is not to combine all\n+admission controllers into a single one. The following controls are explicitly out of scope:\n+\n+-   Defaulting (and mutations in general) - See [No Defaulting](#no-defaulting).\n+\n+-   External dependencies - The policy doesn't express any inter-object relationships, and doesn't\n+    call out to external servers or have plugin points.\n+\n+-   Complex logic or interfield relationships - The goal is to keep the policy simple, both to\n+    implement and reason about. The only exception is fields that explicitly reference others like\n+    VolumeMounts and EnvVarSource, but most fields should be able to be validated without knowledge\n+    of any other Pod fields.\n+\n+## Design\n+\n+### Policy Binding\n+\n+PodRestrictions are applied at the namespace level. Any Pod (or PodTemplate object) must conform to\n+the policies matching the namespace it is created in.\n+\n+```go\n+Binding:\n+    Namespaces        []string\n+    NamespaceSelector *metav1.LabelSelector\n+```\n+\n+#### Justification\n+\n+There are a few different approaches taken by our current policies (see\n+[#60001](https://github.com/kubernetes/kubernetes/issues/60001)).\n+\n+**Bind to Users**: Binding policy to users is the obvious first choice, but has a number of\n+problems. First, users don't typically create pods, they create resources like Deployments that\n+create pods on their behalf. That \"on behalf\" ends up being problematic to implement, especially\n+with issues like confused deputy attacks, third party controllers, and changing policies.\n+\n+**Bind to ServiceAccounts**: To get around the user problem, PodSecurityPolicy adds the ability to\n+bind to the target objects service account. ServiceAccount usage isn't actually authorized\n+separately from namespace usage, so this just ends up being namespace-level authorization. However,\n+because PodSecurityPolicy is authorized by either the source or target user, this ends up being less\n+secure, since the Pod can use it's service account to create pods in other namespaces with the same\n+access level. For instance, using PodSecurityPolicy you cannot have a controller that runs as\n+privileged without opening up all pods it creates to running as privileged.\n+\n+**Namespaced Objects**: This approach has its advantages, but it doesn't scale well to large\n+organizations with many namespaces (without a system on top managing all the policies across\n+namespaces). See extensions below for how we might use namespaced objects with PodRestrictions.\n+\n+**Bind to Pod labels**: Pod labels don't have any authorization of usage without something like\n+PodRestriction, so binding to labels isn't sufficient no it's own.\n+\n+**Bind to Namespaces**: The approach I'm recommending. Cluster level objects use a namespace\n+selector to select the namespaces it applies to. It is consistent with the authorization of create\n+requests, which are authorized at the namespace level. A small number of policies can be managed at\n+the cluster level, and mapped to the namespaces they should apply to.\n+\n+#### Optional Extensions\n+\n+-   Namespaced PodRestrictions - PodRestriction objects can be created in a namespace, so that\n+    namespace admins can place additional restrictions within their namespace.\n+\n+-   Additional Bindings - The NamespaceSelector is not a top-level field so that additional binding\n+    approaches may be added in the future. Examples may include: ServiceAccounts, LabelSelector,\n+    NodeSelector, etc.\n+\n+### Policy Matching - Union or Intersection?\n+\n+**Intersection** refers to the model where a Pod must be valid under all PodRestrictions that\n+pertain to it. The advantage of intersecting is that restrictions can be composed. For example, I\n+could have a policy that frontend & backend Pods cannot share a node, and a separate policy that all\n+production Pods must drop privileges. Without the ability to intersect those policies, I need 3\n+different versions (either, or, both). A problem with this approach is that running a privileged\n+namespace (e.g. kube-system) means that every restriction needs an exception in the\n+NamespaceSelector.\n+\n+**Union** refers to the model where a Pod must be valid under any PodRestriction that is bound to\n+it. This approach makes it easier to reason about what the restrictions on a given namespace are,\n+and easier to add exceptions for namespaces, but can lead to a combinatorial explosion of different\n+policies that can become unmanageable.\n+\n+**Proposal**: Why not both? The default behavior is intersection, but you can punch a hole through\n+for a specific namespace with a union approach. An optional `BindingMode` is added to the Binding\n+which specifies the behavior. A pod must be valid under either:\n+\n+-   ANY `BindingModeAccept` policy that is bound to it, or\n+\n+-   ALL `BindingModeDrop` policies that are bound to it\n+\n+In other words, the Pod is first checked against Accept restrictions, and is accepted if any\n+match. Then, it is checked against the Drop restrictions, and rejected if any don't match. The\n+default behavior at the end is to accept the pod.\n+\n+```\n+Binding:\n+    Mode BindingMode\n+\n+BindingModeAccept\n+BindingModeDrop\n+```\n+\n+### Admission Handling\n+\n+The PodRestriction controller handles all create, update, and patch requests for Pod resources. It\n+is also applied to all (native) resources that create pods via a PodTemplate, including ReplicaSets,\n+ReplicationControllers, DaemonSets, StatefulSets, Deployments, Jobs, etc.\n+\n+#### Justification\n+\n+From a security perspective, it should be sufficient to apply the restriction to Pod requests, so\n+why validate other objects? Fail-fast. By surfacing an error before the controller object is\n+created, the user gets immediate feedback that they are attempting a forbidden operation. The\n+alternative experience is that the controller object creation succeeds, but then the user must\n+follow debug steps to determine why Pods are not being created.\n+\n+#### Optional Extensions\n+\n+New CRDs that hold a PodTemplate and create Pods should be able to be registered against the\n+PodRestriction controller. The registration should include a path (auto-discovery?) to extract the\n+PodTemplate from the CRD.\n+\n+### API Design\n+\n+#### Monolith vs. Modular\n+\n+Our current approach to policies has been \"modular\", i.e. a different controller and API for each\n+\"area\" of features. This has led to some inconsistencies and gaps in our policies, and makes adding\n+new restrictions needlessly complex. I propose we take a \"monolithic\" approach, with a single policy\n+(PodRestriction) for all Pod fields.\n+\n+A monolithic restriction policy (i.e. single resource type) also allows the user to express more\n+complex inter-relationships, such as:\n+\n+-   Pods using a certain volume type must run with reduced privileges\n+\n+-   Pods can only run as privileged if they run with a specific AntiAffinity\n+\n+-   Pods labeled as staging must not run on production nodes.\n+\n+#### No Defaulting\n+\n+There are 2 problems with mutating Pod fields: 1) it becomes hard to compose policies (what happens\n+when the defaults conflict?) and 2) you need a policy ordering mechanism for when a Pod matches\n+multiple policies. Instead, PodRestriction opts out of mutating defaults entirely, and leaves that\n+for a different component to solve ([PodPreset?](../service-catalog/pod-preset.md)).\n+\n+## API Sketch\n+\n+I don't want to hash out all the details of the API (such as which fields are constrained, and what\n+the naming is) in this proposal, but there are a couple high level design goals and takeaways:\n+\n+1.  The PodRestriction API layout mostly mirrors that of the Pod object. This makes the API more\n+    predictable, and easier to manage (i.e. I want to restrict Pod field X, I know how to do that in\n+    the PodRestriction).\n+\n+2.  Restrictions are self-contained. There isn't complex interdependent logic here. If you want more\n+    than basic whitelist / blacklist / pattern matching, then implement a new policy or controller\n+    for that.\n+\n+3.  There are a lot of fields in the PodSpec, and thus this API gets large (see [Monolith\n+    vs. Modular](#monolith-vs-modular)). We don't need to build it all at once, but I think over\n+    time it will eventually grow into the full version.\n+\n+4.  Fields are unrestricted by default. This is important due to the size of the API. If I want to\n+    restrict 1 field, I shouldn't need to write 500 lines to open up all the other fields.\n+\n+```go\n+// This API sketch is partial, and only intended to indicate the the flavor of\n+// the API, not to completely specify it.\n+\n+type PodRestriction struct {\n+    metav1.TypeMeta\n+    metav1.ObjectMeta\n+\n+    Binding *PodRestrictionBinding // Default to cluster-wide\n+\n+    Spec PodRestrictionSpec\n+}\n+\n+type PodRestrictionBinding struct {\n+    Mode              BindingMode  // Default = Drop\n+\n+    Namespaces        []string\n+    NamespaceSelector *meta.LabelSelector\n+}\n+\n+type BindingMode string\n+\n+const (\n+    BindingModeAccept BindingMode = \"Accept\"\n+    BindingModeDrop   BindingMode = \"Drop\"\n+)\n+\n+type PodRestrictionSpec struct {\n+    Metadata *ObjectMetaRestriction\n+\n+    Spec *PodSpecRestriction\n+}\n+\n+type ObjectMetaRestriction struct {\n+\tName        *StringRestriction\n+\tLabels      *StringMapRestriction\n+\tAnnotations *StringMapRestriction\n+}\n+\n+type PodSpecRestriction struct {\n+\tVolumes []VolumeRestriction\n+\n+\tInitContainers []ContainerRestriction // Allow any; default to same as containers?\n+\tContainers     []ContainerRestriction // Allow any\n+\n+\tRestartPolicy                 *StringRestriction\n+\tTerminationGracePeriodSeconds *NumericRestriction\n+\tActiveDeadlineSeconds         *NumericRestriction\n+\tDNSPolicy                     *StringRestriction\n+\tNodeSelector                  *StringMapRestriction\n+\tAutomountServiceAccountToken  *BoolRestriction\n+\tSecurityContext               *PodSecurityContextRestriction\n+\n+    // TODO: Scheduling restrictions are deeply nested structures and require\n+    // more thought.\n+\t// Affinity      *AffinityRestriction\n+\t// SchedulerName *StringRestriction\n+\t// Tolerations   []TolerationRestriction\n+\n+    // ...\n+}\n+\n+type ContainerRestriction struct {\n+\tImage []ImageRestriction  // Allow Any\n+\n+\tImagePullPolicy          *StringRestriction\n+\tSecurityContext          *SecurityContextRestriction\n+\n+    // ...\n+}\n+\n+// QUESTION: Are image restrictions injecting too much login?\n+type ImageRestriction struct {\n+\tRegistry  *StringRestriction\n+\tImage     *StringRestriction\n+\tTag       *StringRestriction\n+}\n+\n+type SecurityContextRestriction struct {\n+\tCapabilities             *CapabilitiesRestriction\n+\tPrivileged               *BoolRestriction\n+\tSELinuxOptions           *SELinuxOptionsRestriction\n+\tRunAsUser                *NumericRestriction\n+\tRunAsGroup               *NumericRestriction\n+\tRunAsNonRoot             *BoolRestriction\n+\tReadOnlyRootFilesystem   *BoolRestriction\n+\tAllowPrivilegeEscalation *BoolRestriction\n+}\n+\n+type SELinuxOptionsRestriction struct {\n+\tPointerRestriction\n+\tUser  *StringRestriction\n+\tRole  *StringRestriction\n+\tType  *StringRestriction\n+\tLevel *StringRestriction\n+}\n+\n+type CapabilitiesRestriction struct {\n+\tPointerRestriction\n+\tAdd  *StringListRestriction\n+\tDrop *StringListRestriction","path":"contributors/design-proposals/auth/pod-restriction.md","position":303,"original_position":303,"commit_id":"05d1e3f4ed909e4c9ae1a4b35b820f1f6a85d592","original_commit_id":"05d1e3f4ed909e4c9ae1a4b35b820f1f6a85d592","user":{"login":"bgrant0607","id":7725777,"avatar_url":"https://avatars1.githubusercontent.com/u/7725777?v=4","gravatar_id":"","url":"https://api.github.com/users/bgrant0607","html_url":"https://github.com/bgrant0607","followers_url":"https://api.github.com/users/bgrant0607/followers","following_url":"https://api.github.com/users/bgrant0607/following{/other_user}","gists_url":"https://api.github.com/users/bgrant0607/gists{/gist_id}","starred_url":"https://api.github.com/users/bgrant0607/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bgrant0607/subscriptions","organizations_url":"https://api.github.com/users/bgrant0607/orgs","repos_url":"https://api.github.com/users/bgrant0607/repos","events_url":"https://api.github.com/users/bgrant0607/events{/privacy}","received_events_url":"https://api.github.com/users/bgrant0607/received_events","type":"User","site_admin":false},"body":"Again, does this mean deny or elide?","created_at":"2018-03-27T01:20:15Z","updated_at":"2018-03-27T01:34:43Z","html_url":"https://github.com/kubernetes/community/pull/1950#discussion_r177281591","pull_request_url":"https://api.github.com/repos/kubernetes/community/pulls/1950","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments/177281591"},"html":{"href":"https://github.com/kubernetes/community/pull/1950#discussion_r177281591"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/community/pulls/1950"}}},"pull_request":{"url":"https://api.github.com/repos/kubernetes/community/pulls/1950","id":176392557,"html_url":"https://github.com/kubernetes/community/pull/1950","diff_url":"https://github.com/kubernetes/community/pull/1950.diff","patch_url":"https://github.com/kubernetes/community/pull/1950.patch","issue_url":"https://api.github.com/repos/kubernetes/community/issues/1950","number":1950,"state":"open","locked":false,"title":"[RFC] PodRestriction proposal","user":{"login":"tallclair","id":29742491,"avatar_url":"https://avatars1.githubusercontent.com/u/29742491?v=4","gravatar_id":"","url":"https://api.github.com/users/tallclair","html_url":"https://github.com/tallclair","followers_url":"https://api.github.com/users/tallclair/followers","following_url":"https://api.github.com/users/tallclair/following{/other_user}","gists_url":"https://api.github.com/users/tallclair/gists{/gist_id}","starred_url":"https://api.github.com/users/tallclair/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tallclair/subscriptions","organizations_url":"https://api.github.com/users/tallclair/orgs","repos_url":"https://api.github.com/users/tallclair/repos","events_url":"https://api.github.com/users/tallclair/events{/privacy}","received_events_url":"https://api.github.com/users/tallclair/received_events","type":"User","site_admin":false},"body":"This is a proposal for a new policy object (and associated admission controller) that adds basic restrictions to most fields on a Pod. This is motivated by a desire to unify the various policies we have now (which all behave in slightly different ways), and to unblock expanding the set of restricted fields. Furthermore, it aims to solve some of the key usability problems with PodSecurityPolicies, such as the inability to compose policies and the difficulty in binding policies.\r\n\r\n**Help Wanted:** I don't actually have time to own the implementation of this proposal, so I'm looking for community members who are interested in realizing this. If we decide to move forward with this approach, my hope is that we can consolidate some of the efforts around other new restriction policies.\r\n\r\n**Question**: Should this be rolled up into the overall [policy proposal](https://docs.google.com/document/d/1Ht8wpj4j9YfAA7aVv9Yn3Ci1T_MLMWt0DBr0QmxI2OM/edit#heading=h.6l8mozsiyzb1) or something like [OPA](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html)? Alternatively, should this use a path-based OPA-style API?\r\n\r\nFix for https://github.com/kubernetes/kubernetes/issues/60001\r\n\r\n/sig auth","created_at":"2018-03-21T06:41:53Z","updated_at":"2018-03-27T01:34:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bc490a51882fd6388a07517b984a2307604c1f16","assignee":null,"assignees":[],"requested_reviewers":[{"login":"liggitt","id":980082,"avatar_url":"https://avatars0.githubusercontent.com/u/980082?v=4","gravatar_id":"","url":"https://api.github.com/users/liggitt","html_url":"https://github.com/liggitt","followers_url":"https://api.github.com/users/liggitt/followers","following_url":"https://api.github.com/users/liggitt/following{/other_user}","gists_url":"https://api.github.com/users/liggitt/gists{/gist_id}","starred_url":"https://api.github.com/users/liggitt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/liggitt/subscriptions","organizations_url":"https://api.github.com/users/liggitt/orgs","repos_url":"https://api.github.com/users/liggitt/repos","events_url":"https://api.github.com/users/liggitt/events{/privacy}","received_events_url":"https://api.github.com/users/liggitt/received_events","type":"User","site_admin":false},{"login":"ericchiang","id":2342749,"avatar_url":"https://avatars1.githubusercontent.com/u/2342749?v=4","gravatar_id":"","url":"https://api.github.com/users/ericchiang","html_url":"https://github.com/ericchiang","followers_url":"https://api.github.com/users/ericchiang/followers","following_url":"https://api.github.com/users/ericchiang/following{/other_user}","gists_url":"https://api.github.com/users/ericchiang/gists{/gist_id}","starred_url":"https://api.github.com/users/ericchiang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ericchiang/subscriptions","organizations_url":"https://api.github.com/users/ericchiang/orgs","repos_url":"https://api.github.com/users/ericchiang/repos","events_url":"https://api.github.com/users/ericchiang/events{/privacy}","received_events_url":"https://api.github.com/users/ericchiang/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":653942629,"url":"https://api.github.com/repos/kubernetes/community/labels/approved","name":"approved","color":"0ffa16","default":false},{"id":486032367,"url":"https://api.github.com/repos/kubernetes/community/labels/cncf-cla:%20yes","name":"cncf-cla: yes","color":"bfe5bf","default":false},{"id":653942785,"url":"https://api.github.com/repos/kubernetes/community/labels/kind/design","name":"kind/design","color":"c7def8","default":false},{"id":653942870,"url":"https://api.github.com/repos/kubernetes/community/labels/sig/auth","name":"sig/auth","color":"d2b48c","default":false},{"id":653942904,"url":"https://api.github.com/repos/kubernetes/community/labels/sig/scheduling","name":"sig/scheduling","color":"d2b48c","default":false},{"id":653942910,"url":"https://api.github.com/repos/kubernetes/community/labels/size/L","name":"size/L","color":"ee9900","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/community/pulls/1950/commits","review_comments_url":"https://api.github.com/repos/kubernetes/community/pulls/1950/comments","review_comment_url":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/community/issues/1950/comments","statuses_url":"https://api.github.com/repos/kubernetes/community/statuses/05d1e3f4ed909e4c9ae1a4b35b820f1f6a85d592","head":{"label":"tallclair:pod-restrict","ref":"pod-restrict","sha":"05d1e3f4ed909e4c9ae1a4b35b820f1f6a85d592","user":{"login":"tallclair","id":29742491,"avatar_url":"https://avatars1.githubusercontent.com/u/29742491?v=4","gravatar_id":"","url":"https://api.github.com/users/tallclair","html_url":"https://github.com/tallclair","followers_url":"https://api.github.com/users/tallclair/followers","following_url":"https://api.github.com/users/tallclair/following{/other_user}","gists_url":"https://api.github.com/users/tallclair/gists{/gist_id}","starred_url":"https://api.github.com/users/tallclair/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tallclair/subscriptions","organizations_url":"https://api.github.com/users/tallclair/orgs","repos_url":"https://api.github.com/users/tallclair/repos","events_url":"https://api.github.com/users/tallclair/events{/privacy}","received_events_url":"https://api.github.com/users/tallclair/received_events","type":"User","site_admin":false},"repo":{"id":96567087,"name":"k8s-community","full_name":"tallclair/k8s-community","owner":{"login":"tallclair","id":29742491,"avatar_url":"https://avatars1.githubusercontent.com/u/29742491?v=4","gravatar_id":"","url":"https://api.github.com/users/tallclair","html_url":"https://github.com/tallclair","followers_url":"https://api.github.com/users/tallclair/followers","following_url":"https://api.github.com/users/tallclair/following{/other_user}","gists_url":"https://api.github.com/users/tallclair/gists{/gist_id}","starred_url":"https://api.github.com/users/tallclair/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tallclair/subscriptions","organizations_url":"https://api.github.com/users/tallclair/orgs","repos_url":"https://api.github.com/users/tallclair/repos","events_url":"https://api.github.com/users/tallclair/events{/privacy}","received_events_url":"https://api.github.com/users/tallclair/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tallclair/k8s-community","description":"Kubernetes community content","fork":true,"url":"https://api.github.com/repos/tallclair/k8s-community","forks_url":"https://api.github.com/repos/tallclair/k8s-community/forks","keys_url":"https://api.github.com/repos/tallclair/k8s-community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tallclair/k8s-community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tallclair/k8s-community/teams","hooks_url":"https://api.github.com/repos/tallclair/k8s-community/hooks","issue_events_url":"https://api.github.com/repos/tallclair/k8s-community/issues/events{/number}","events_url":"https://api.github.com/repos/tallclair/k8s-community/events","assignees_url":"https://api.github.com/repos/tallclair/k8s-community/assignees{/user}","branches_url":"https://api.github.com/repos/tallclair/k8s-community/branches{/branch}","tags_url":"https://api.github.com/repos/tallclair/k8s-community/tags","blobs_url":"https://api.github.com/repos/tallclair/k8s-community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tallclair/k8s-community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tallclair/k8s-community/git/refs{/sha}","trees_url":"https://api.github.com/repos/tallclair/k8s-community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tallclair/k8s-community/statuses/{sha}","languages_url":"https://api.github.com/repos/tallclair/k8s-community/languages","stargazers_url":"https://api.github.com/repos/tallclair/k8s-community/stargazers","contributors_url":"https://api.github.com/repos/tallclair/k8s-community/contributors","subscribers_url":"https://api.github.com/repos/tallclair/k8s-community/subscribers","subscription_url":"https://api.github.com/repos/tallclair/k8s-community/subscription","commits_url":"https://api.github.com/repos/tallclair/k8s-community/commits{/sha}","git_commits_url":"https://api.github.com/repos/tallclair/k8s-community/git/commits{/sha}","comments_url":"https://api.github.com/repos/tallclair/k8s-community/comments{/number}","issue_comment_url":"https://api.github.com/repos/tallclair/k8s-community/issues/comments{/number}","contents_url":"https://api.github.com/repos/tallclair/k8s-community/contents/{+path}","compare_url":"https://api.github.com/repos/tallclair/k8s-community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tallclair/k8s-community/merges","archive_url":"https://api.github.com/repos/tallclair/k8s-community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tallclair/k8s-community/downloads","issues_url":"https://api.github.com/repos/tallclair/k8s-community/issues{/number}","pulls_url":"https://api.github.com/repos/tallclair/k8s-community/pulls{/number}","milestones_url":"https://api.github.com/repos/tallclair/k8s-community/milestones{/number}","notifications_url":"https://api.github.com/repos/tallclair/k8s-community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tallclair/k8s-community/labels{/name}","releases_url":"https://api.github.com/repos/tallclair/k8s-community/releases{/id}","deployments_url":"https://api.github.com/repos/tallclair/k8s-community/deployments","created_at":"2017-07-07T18:39:48Z","updated_at":"2018-03-21T06:31:02Z","pushed_at":"2018-03-21T06:57:01Z","git_url":"git://github.com/tallclair/k8s-community.git","ssh_url":"git@github.com:tallclair/k8s-community.git","clone_url":"https://github.com/tallclair/k8s-community.git","svn_url":"https://github.com/tallclair/k8s-community","homepage":"","size":19647,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"kubernetes:master","ref":"master","sha":"e926accd78d2822b49589e511cd31a52b57c30b8","user":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars2.githubusercontent.com/u/13629408?v=4","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"repo":{"id":57939112,"name":"community","full_name":"kubernetes/community","owner":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars2.githubusercontent.com/u/13629408?v=4","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kubernetes/community","description":"Kubernetes community content","fork":false,"url":"https://api.github.com/repos/kubernetes/community","forks_url":"https://api.github.com/repos/kubernetes/community/forks","keys_url":"https://api.github.com/repos/kubernetes/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes/community/teams","hooks_url":"https://api.github.com/repos/kubernetes/community/hooks","issue_events_url":"https://api.github.com/repos/kubernetes/community/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes/community/events","assignees_url":"https://api.github.com/repos/kubernetes/community/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes/community/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes/community/tags","blobs_url":"https://api.github.com/repos/kubernetes/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes/community/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes/community/languages","stargazers_url":"https://api.github.com/repos/kubernetes/community/stargazers","contributors_url":"https://api.github.com/repos/kubernetes/community/contributors","subscribers_url":"https://api.github.com/repos/kubernetes/community/subscribers","subscription_url":"https://api.github.com/repos/kubernetes/community/subscription","commits_url":"https://api.github.com/repos/kubernetes/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes/community/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes/community/merges","archive_url":"https://api.github.com/repos/kubernetes/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes/community/downloads","issues_url":"https://api.github.com/repos/kubernetes/community/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes/community/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes/community/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes/community/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes/community/releases{/id}","deployments_url":"https://api.github.com/repos/kubernetes/community/deployments","created_at":"2016-05-03T03:36:05Z","updated_at":"2018-03-26T23:32:50Z","pushed_at":"2018-03-27T01:05:29Z","git_url":"git://github.com/kubernetes/community.git","ssh_url":"git@github.com:kubernetes/community.git","clone_url":"https://github.com/kubernetes/community.git","svn_url":"https://github.com/kubernetes/community","homepage":"","size":20319,"stargazers_count":1586,"watchers_count":1586,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1035,"mirror_url":null,"archived":false,"open_issues_count":191,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":1035,"open_issues":191,"watchers":1586,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/1950"},"html":{"href":"https://github.com/kubernetes/community/pull/1950"},"issue":{"href":"https://api.github.com/repos/kubernetes/community/issues/1950"},"comments":{"href":"https://api.github.com/repos/kubernetes/community/issues/1950/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/community/pulls/1950/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/community/pulls/1950/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/community/statuses/05d1e3f4ed909e4c9ae1a4b35b820f1f6a85d592"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"bgrant0607"}}