accept_env.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tailssh
  4. import (
  5. "fmt"
  6. "slices"
  7. "strings"
  8. )
  9. // filterEnv filters a passed in environ string slice (a slice with strings
  10. // representing environment variables in the form "key=value") based on
  11. // the supplied slice of acceptEnv values.
  12. //
  13. // acceptEnv is a slice of environment variable names that are allowlisted
  14. // for the SSH rule in the policy file.
  15. //
  16. // acceptEnv values may contain * and ? wildcard characters which match against
  17. // zero or one or more characters and a single character respectively.
  18. func filterEnv(acceptEnv []string, environ []string) ([]string, error) {
  19. var acceptedPairs []string
  20. // Quick return if we have an empty list.
  21. if acceptEnv == nil || len(acceptEnv) == 0 {
  22. return acceptedPairs, nil
  23. }
  24. for _, envPair := range environ {
  25. variableName, _, ok := strings.Cut(envPair, "=")
  26. if !ok {
  27. return nil, fmt.Errorf(`invalid environment variable: %q. Variables must be in "KEY=VALUE" format`, envPair)
  28. }
  29. // Short circuit if we have a direct match between the environment
  30. // variable and an AcceptEnv value.
  31. if slices.Contains(acceptEnv, variableName) {
  32. acceptedPairs = append(acceptedPairs, envPair)
  33. continue
  34. }
  35. // Otherwise check if we have a wildcard pattern that matches.
  36. if matchAcceptEnv(acceptEnv, variableName) {
  37. acceptedPairs = append(acceptedPairs, envPair)
  38. continue
  39. }
  40. }
  41. return acceptedPairs, nil
  42. }
  43. // matchAcceptEnv is a convenience function that wraps calling matchAcceptEnvPattern
  44. // with every value in acceptEnv for a given env that is being matched against.
  45. func matchAcceptEnv(acceptEnv []string, env string) bool {
  46. for _, pattern := range acceptEnv {
  47. if matchAcceptEnvPattern(pattern, env) {
  48. return true
  49. }
  50. }
  51. return false
  52. }
  53. // matchAcceptEnvPattern returns true if the pattern matches against the target string.
  54. // Patterns may include * and ? wildcard characters which match against zero or one or
  55. // more characters and a single character respectively.
  56. func matchAcceptEnvPattern(pattern string, target string) bool {
  57. patternIdx := 0
  58. targetIdx := 0
  59. for {
  60. // If we are at the end of the pattern we can only have a match if we
  61. // are also at the end of the target.
  62. if patternIdx >= len(pattern) {
  63. return targetIdx >= len(target)
  64. }
  65. if pattern[patternIdx] == '*' {
  66. // Optimization to skip through any repeated asterisks as they
  67. // have the same net effect on our search.
  68. for patternIdx < len(pattern) {
  69. if pattern[patternIdx] != '*' {
  70. break
  71. }
  72. patternIdx++
  73. }
  74. // We are at the end of the pattern after matching the asterisk,
  75. // implying a match.
  76. if patternIdx >= len(pattern) {
  77. return true
  78. }
  79. // Search through the target sequentially for the next character
  80. // from the pattern string, recursing into matchAcceptEnvPattern
  81. // to try and find a match.
  82. for ; targetIdx < len(target); targetIdx++ {
  83. if matchAcceptEnvPattern(pattern[patternIdx:], target[targetIdx:]) {
  84. return true
  85. }
  86. }
  87. // No match after searching through the entire target.
  88. return false
  89. }
  90. if targetIdx >= len(target) {
  91. return false
  92. }
  93. if pattern[patternIdx] != '?' && pattern[patternIdx] != target[targetIdx] {
  94. return false
  95. }
  96. patternIdx++
  97. targetIdx++
  98. }
  99. }