firewall.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package gce
  14. import (
  15. "fmt"
  16. "net/http"
  17. "strconv"
  18. "strings"
  19. "time"
  20. compute "google.golang.org/api/compute/v1"
  21. "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/util/sets"
  23. "k8s.io/apimachinery/pkg/util/wait"
  24. cloudprovider "k8s.io/cloud-provider"
  25. "k8s.io/kubernetes/test/e2e/framework"
  26. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  27. gcecloud "k8s.io/legacy-cloud-providers/gce"
  28. )
  29. // MakeFirewallNameForLBService return the expected firewall name for a LB service.
  30. // This should match the formatting of makeFirewallName() in pkg/cloudprovider/providers/gce/gce_loadbalancer.go
  31. func MakeFirewallNameForLBService(name string) string {
  32. return fmt.Sprintf("k8s-fw-%s", name)
  33. }
  34. // ConstructFirewallForLBService returns the expected GCE firewall rule for a loadbalancer type service
  35. func ConstructFirewallForLBService(svc *v1.Service, nodeTag string) *compute.Firewall {
  36. if svc.Spec.Type != v1.ServiceTypeLoadBalancer {
  37. framework.Failf("can not construct firewall rule for non-loadbalancer type service")
  38. }
  39. fw := compute.Firewall{}
  40. fw.Name = MakeFirewallNameForLBService(cloudprovider.DefaultLoadBalancerName(svc))
  41. fw.TargetTags = []string{nodeTag}
  42. if svc.Spec.LoadBalancerSourceRanges == nil {
  43. fw.SourceRanges = []string{"0.0.0.0/0"}
  44. } else {
  45. fw.SourceRanges = svc.Spec.LoadBalancerSourceRanges
  46. }
  47. for _, sp := range svc.Spec.Ports {
  48. fw.Allowed = append(fw.Allowed, &compute.FirewallAllowed{
  49. IPProtocol: strings.ToLower(string(sp.Protocol)),
  50. Ports: []string{strconv.Itoa(int(sp.Port))},
  51. })
  52. }
  53. return &fw
  54. }
  55. // MakeHealthCheckFirewallNameForLBService returns the firewall name used by the GCE load
  56. // balancers for performing health checks.
  57. func MakeHealthCheckFirewallNameForLBService(clusterID, name string, isNodesHealthCheck bool) string {
  58. return gcecloud.MakeHealthCheckFirewallName(clusterID, name, isNodesHealthCheck)
  59. }
  60. // ConstructHealthCheckFirewallForLBService returns the expected GCE firewall rule for a loadbalancer type service
  61. func ConstructHealthCheckFirewallForLBService(clusterID string, svc *v1.Service, nodeTag string, isNodesHealthCheck bool) *compute.Firewall {
  62. if svc.Spec.Type != v1.ServiceTypeLoadBalancer {
  63. framework.Failf("can not construct firewall rule for non-loadbalancer type service")
  64. }
  65. fw := compute.Firewall{}
  66. fw.Name = MakeHealthCheckFirewallNameForLBService(clusterID, cloudprovider.DefaultLoadBalancerName(svc), isNodesHealthCheck)
  67. fw.TargetTags = []string{nodeTag}
  68. fw.SourceRanges = gcecloud.LoadBalancerSrcRanges()
  69. healthCheckPort := gcecloud.GetNodesHealthCheckPort()
  70. if !isNodesHealthCheck {
  71. healthCheckPort = svc.Spec.HealthCheckNodePort
  72. }
  73. fw.Allowed = []*compute.FirewallAllowed{
  74. {
  75. IPProtocol: "tcp",
  76. Ports: []string{fmt.Sprintf("%d", healthCheckPort)},
  77. },
  78. }
  79. return &fw
  80. }
  81. // GetInstancePrefix returns the INSTANCE_PREFIX env we set for e2e cluster.
  82. // From cluster/gce/config-test.sh, master name is set up using below format:
  83. // MASTER_NAME="${INSTANCE_PREFIX}-master"
  84. func GetInstancePrefix(masterName string) (string, error) {
  85. if !strings.HasSuffix(masterName, "-master") {
  86. return "", fmt.Errorf("unexpected master name format: %v", masterName)
  87. }
  88. return masterName[:len(masterName)-7], nil
  89. }
  90. // GetClusterName returns the CLUSTER_NAME env we set for e2e cluster.
  91. // From cluster/gce/config-test.sh, cluster name is set up using below format:
  92. // CLUSTER_NAME="${CLUSTER_NAME:-${INSTANCE_PREFIX}}"
  93. func GetClusterName(instancePrefix string) string {
  94. return instancePrefix
  95. }
  96. // GetE2eFirewalls returns all firewall rules we create for an e2e cluster.
  97. // From cluster/gce/util.sh, all firewall rules should be consistent with the ones created by startup scripts.
  98. func GetE2eFirewalls(masterName, masterTag, nodeTag, network, clusterIPRange string) []*compute.Firewall {
  99. instancePrefix, err := GetInstancePrefix(masterName)
  100. framework.ExpectNoError(err)
  101. clusterName := GetClusterName(instancePrefix)
  102. fws := []*compute.Firewall{}
  103. fws = append(fws, &compute.Firewall{
  104. Name: clusterName + "-default-internal-master",
  105. SourceRanges: []string{"10.0.0.0/8"},
  106. TargetTags: []string{masterTag},
  107. Allowed: []*compute.FirewallAllowed{
  108. {
  109. IPProtocol: "tcp",
  110. Ports: []string{"1-2379"},
  111. },
  112. {
  113. IPProtocol: "tcp",
  114. Ports: []string{"2382-65535"},
  115. },
  116. {
  117. IPProtocol: "udp",
  118. Ports: []string{"1-65535"},
  119. },
  120. {
  121. IPProtocol: "icmp",
  122. },
  123. },
  124. })
  125. fws = append(fws, &compute.Firewall{
  126. Name: clusterName + "-default-internal-node",
  127. SourceRanges: []string{"10.0.0.0/8"},
  128. TargetTags: []string{nodeTag},
  129. Allowed: []*compute.FirewallAllowed{
  130. {
  131. IPProtocol: "tcp",
  132. Ports: []string{"1-65535"},
  133. },
  134. {
  135. IPProtocol: "udp",
  136. Ports: []string{"1-65535"},
  137. },
  138. {
  139. IPProtocol: "icmp",
  140. },
  141. },
  142. })
  143. fws = append(fws, &compute.Firewall{
  144. Name: network + "-default-ssh",
  145. SourceRanges: []string{"0.0.0.0/0"},
  146. Allowed: []*compute.FirewallAllowed{
  147. {
  148. IPProtocol: "tcp",
  149. Ports: []string{"22"},
  150. },
  151. },
  152. })
  153. fws = append(fws, &compute.Firewall{
  154. Name: masterName + "-etcd",
  155. SourceTags: []string{masterTag},
  156. TargetTags: []string{masterTag},
  157. Allowed: []*compute.FirewallAllowed{
  158. {
  159. IPProtocol: "tcp",
  160. Ports: []string{"2380"},
  161. },
  162. {
  163. IPProtocol: "tcp",
  164. Ports: []string{"2381"},
  165. },
  166. },
  167. })
  168. fws = append(fws, &compute.Firewall{
  169. Name: masterName + "-https",
  170. SourceRanges: []string{"0.0.0.0/0"},
  171. TargetTags: []string{masterTag},
  172. Allowed: []*compute.FirewallAllowed{
  173. {
  174. IPProtocol: "tcp",
  175. Ports: []string{"443"},
  176. },
  177. },
  178. })
  179. fws = append(fws, &compute.Firewall{
  180. Name: nodeTag + "-all",
  181. SourceRanges: []string{clusterIPRange},
  182. TargetTags: []string{nodeTag},
  183. Allowed: []*compute.FirewallAllowed{
  184. {
  185. IPProtocol: "tcp",
  186. },
  187. {
  188. IPProtocol: "udp",
  189. },
  190. {
  191. IPProtocol: "icmp",
  192. },
  193. {
  194. IPProtocol: "esp",
  195. },
  196. {
  197. IPProtocol: "ah",
  198. },
  199. {
  200. IPProtocol: "sctp",
  201. },
  202. },
  203. })
  204. fws = append(fws, &compute.Firewall{
  205. Name: nodeTag + "-" + instancePrefix + "-http-alt",
  206. SourceRanges: []string{"0.0.0.0/0"},
  207. TargetTags: []string{nodeTag},
  208. Allowed: []*compute.FirewallAllowed{
  209. {
  210. IPProtocol: "tcp",
  211. Ports: []string{"80"},
  212. },
  213. {
  214. IPProtocol: "tcp",
  215. Ports: []string{"8080"},
  216. },
  217. },
  218. })
  219. fws = append(fws, &compute.Firewall{
  220. Name: nodeTag + "-" + instancePrefix + "-nodeports",
  221. SourceRanges: []string{"0.0.0.0/0"},
  222. TargetTags: []string{nodeTag},
  223. Allowed: []*compute.FirewallAllowed{
  224. {
  225. IPProtocol: "tcp",
  226. Ports: []string{"30000-32767"},
  227. },
  228. {
  229. IPProtocol: "udp",
  230. Ports: []string{"30000-32767"},
  231. },
  232. },
  233. })
  234. return fws
  235. }
  236. // PackProtocolsPortsFromFirewall packs protocols and ports in an unified way for verification.
  237. func PackProtocolsPortsFromFirewall(alloweds []*compute.FirewallAllowed) []string {
  238. protocolPorts := []string{}
  239. for _, allowed := range alloweds {
  240. for _, port := range allowed.Ports {
  241. protocolPorts = append(protocolPorts, strings.ToLower(allowed.IPProtocol+"/"+port))
  242. }
  243. }
  244. return protocolPorts
  245. }
  246. type portRange struct {
  247. protocol string
  248. min, max int
  249. }
  250. func toPortRange(s string) (pr portRange, err error) {
  251. protoPorts := strings.Split(s, "/")
  252. // Set protocol
  253. pr.protocol = strings.ToUpper(protoPorts[0])
  254. if len(protoPorts) != 2 {
  255. return pr, fmt.Errorf("expected a single '/' in %q", s)
  256. }
  257. ports := strings.Split(protoPorts[1], "-")
  258. switch len(ports) {
  259. case 1:
  260. v, err := strconv.Atoi(ports[0])
  261. if err != nil {
  262. return pr, err
  263. }
  264. pr.min, pr.max = v, v
  265. case 2:
  266. start, err := strconv.Atoi(ports[0])
  267. if err != nil {
  268. return pr, err
  269. }
  270. end, err := strconv.Atoi(ports[1])
  271. if err != nil {
  272. return pr, err
  273. }
  274. pr.min, pr.max = start, end
  275. default:
  276. return pr, fmt.Errorf("unexpected range value %q", protoPorts[1])
  277. }
  278. return pr, nil
  279. }
  280. // isPortsSubset asserts that the "requiredPorts" are covered by the "coverage" ports.
  281. // requiredPorts - must be single-port, examples: 'tcp/50', 'udp/80'.
  282. // coverage - single or port-range values, example: 'tcp/50', 'udp/80-1000'.
  283. // Returns true if every requiredPort exists in the list of coverage rules.
  284. func isPortsSubset(requiredPorts, coverage []string) error {
  285. for _, reqPort := range requiredPorts {
  286. rRange, err := toPortRange(reqPort)
  287. if err != nil {
  288. return err
  289. }
  290. if rRange.min != rRange.max {
  291. return fmt.Errorf("requiring a range is not supported: %q", reqPort)
  292. }
  293. var covered bool
  294. for _, c := range coverage {
  295. cRange, err := toPortRange(c)
  296. if err != nil {
  297. return err
  298. }
  299. if rRange.protocol != cRange.protocol {
  300. continue
  301. }
  302. if rRange.min >= cRange.min && rRange.min <= cRange.max {
  303. covered = true
  304. break
  305. }
  306. }
  307. if !covered {
  308. return fmt.Errorf("%q is not covered by %v", reqPort, coverage)
  309. }
  310. }
  311. return nil
  312. }
  313. // SameStringArray verifies whether two string arrays have the same strings, return error if not.
  314. // Order does not matter.
  315. // When `include` is set to true, verifies whether result includes all elements from expected.
  316. func SameStringArray(result, expected []string, include bool) error {
  317. res := sets.NewString(result...)
  318. exp := sets.NewString(expected...)
  319. if !include {
  320. diff := res.Difference(exp)
  321. if len(diff) != 0 {
  322. return fmt.Errorf("found differences: %v", diff)
  323. }
  324. } else {
  325. if !res.IsSuperset(exp) {
  326. return fmt.Errorf("some elements are missing: expected %v, got %v", expected, result)
  327. }
  328. }
  329. return nil
  330. }
  331. // VerifyFirewallRule verifies whether the result firewall is consistent with the expected firewall.
  332. // When `portsSubset` is false, match given ports exactly. Otherwise, only check ports are included.
  333. func VerifyFirewallRule(res, exp *compute.Firewall, network string, portsSubset bool) error {
  334. if res == nil || exp == nil {
  335. return fmt.Errorf("res and exp must not be nil")
  336. }
  337. if res.Name != exp.Name {
  338. return fmt.Errorf("incorrect name: %v, expected %v", res.Name, exp.Name)
  339. }
  340. // Sample Network value: https://www.googleapis.com/compute/v1/projects/{project-id}/global/networks/e2e
  341. if !strings.HasSuffix(res.Network, "/"+network) {
  342. return fmt.Errorf("incorrect network: %v, expected ends with: %v", res.Network, "/"+network)
  343. }
  344. actualPorts := PackProtocolsPortsFromFirewall(res.Allowed)
  345. expPorts := PackProtocolsPortsFromFirewall(exp.Allowed)
  346. if portsSubset {
  347. if err := isPortsSubset(expPorts, actualPorts); err != nil {
  348. return fmt.Errorf("incorrect allowed protocol ports: %v", err)
  349. }
  350. } else {
  351. if err := SameStringArray(actualPorts, expPorts, false); err != nil {
  352. return fmt.Errorf("incorrect allowed protocols ports: %v", err)
  353. }
  354. }
  355. if err := SameStringArray(res.SourceRanges, exp.SourceRanges, false); err != nil {
  356. return fmt.Errorf("incorrect source ranges %v, expected %v: %v", res.SourceRanges, exp.SourceRanges, err)
  357. }
  358. if err := SameStringArray(res.SourceTags, exp.SourceTags, false); err != nil {
  359. return fmt.Errorf("incorrect source tags %v, expected %v: %v", res.SourceTags, exp.SourceTags, err)
  360. }
  361. if err := SameStringArray(res.TargetTags, exp.TargetTags, false); err != nil {
  362. return fmt.Errorf("incorrect target tags %v, expected %v: %v", res.TargetTags, exp.TargetTags, err)
  363. }
  364. return nil
  365. }
  366. // WaitForFirewallRule waits for the specified firewall existence
  367. func WaitForFirewallRule(gceCloud *gcecloud.Cloud, fwName string, exist bool, timeout time.Duration) (*compute.Firewall, error) {
  368. e2elog.Logf("Waiting up to %v for firewall %v exist=%v", timeout, fwName, exist)
  369. var fw *compute.Firewall
  370. var err error
  371. condition := func() (bool, error) {
  372. fw, err = gceCloud.GetFirewall(fwName)
  373. if err != nil && exist ||
  374. err == nil && !exist ||
  375. err != nil && !exist && !IsGoogleAPIHTTPErrorCode(err, http.StatusNotFound) {
  376. return false, nil
  377. }
  378. return true, nil
  379. }
  380. if err := wait.PollImmediate(5*time.Second, timeout, condition); err != nil {
  381. return nil, fmt.Errorf("error waiting for firewall %v exist=%v", fwName, exist)
  382. }
  383. return fw, nil
  384. }