Skip to content

Commit c516df3

Browse files
committed
Distros: Harden security
- Require distro maintainer group membership for some distro endpoints, require admin for others - Require that URIs start with "https:" to prevent e.g. having "javascript:" in there
1 parent 1594efc commit c516df3

File tree

2 files changed

+45
-32
lines changed

2 files changed

+45
-32
lines changed

src/Distribution/Server/Features/Distro.hs

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{-# LANGUAGE RankNTypes, NamedFieldPuns, RecordWildCards #-}
1+
{-# LANGUAGE RankNTypes, NamedFieldPuns, RecordWildCards, RecursiveDo #-}
22
module Distribution.Server.Features.Distro (
33
DistroFeature(..),
44
DistroResource(..),
@@ -20,6 +20,7 @@ import Distribution.Text (display, simpleParse)
2020
import Distribution.Package
2121

2222
import Data.List (intercalate)
23+
import qualified Data.Text as T
2324
import Text.CSV (parseCSV)
2425

2526
-- TODO:
@@ -29,7 +30,6 @@ import Text.CSV (parseCSV)
2930
data DistroFeature = DistroFeature {
3031
distroFeatureInterface :: HackageFeature,
3132
distroResource :: DistroResource,
32-
maintainersGroup :: DynamicPath -> IO (Maybe UserGroup),
3333
queryPackageStatus :: forall m. MonadIO m => PackageName -> m [(DistroName, DistroPackageInfo)]
3434
}
3535

@@ -48,8 +48,28 @@ initDistroFeature :: ServerEnv
4848
initDistroFeature ServerEnv{serverStateDir} = do
4949
distrosState <- distrosStateComponent serverStateDir
5050

51-
return $ \user core -> do
52-
let feature = distroFeature user core distrosState
51+
return $ \user@UserFeature{adminGroup, groupResourcesAt} core@CoreFeature{coreResource} -> do
52+
rec
53+
let
54+
maintainersUserGroup :: DistroName -> UserGroup
55+
maintainersUserGroup name =
56+
UserGroup {
57+
groupDesc = maintainerGroupDescription name,
58+
queryUserGroup = queryState distrosState $ GetDistroMaintainers name,
59+
addUserToGroup = updateState distrosState . AddDistroMaintainer name,
60+
removeUserFromGroup = updateState distrosState . RemoveDistroMaintainer name,
61+
groupsAllowedToAdd = [adminGroup],
62+
groupsAllowedToDelete = [adminGroup]
63+
}
64+
feature = distroFeature user core distrosState maintainersGroupResource maintainersUserGroup
65+
distroNames <- queryState distrosState EnumerateDistros
66+
(_maintainersGroup, maintainersGroupResource) <-
67+
groupResourcesAt "/distro/:package/maintainers"
68+
maintainersUserGroup
69+
(\distroName -> [("package", display distroName)])
70+
(packageInPath coreResource)
71+
distroNames
72+
5373
return feature
5474

5575
distrosStateComponent :: FilePath -> IO (StateComponent AcidState Distros)
@@ -68,15 +88,21 @@ distrosStateComponent stateDir = do
6888
distroFeature :: UserFeature
6989
-> CoreFeature
7090
-> StateComponent AcidState Distros
91+
-> GroupResource
92+
-> (DistroName -> UserGroup)
7193
-> DistroFeature
7294
distroFeature UserFeature{..}
7395
CoreFeature{coreResource=CoreResource{packageInPath}}
7496
distrosState
97+
maintainersGroupResource
98+
distroGroup
7599
= DistroFeature{..}
76100
where
77101
distroFeatureInterface = (emptyHackageFeature "distro") {
78102
featureResources =
79-
map ($ distroResource) [
103+
groupResource maintainersGroupResource
104+
: groupUserResource maintainersGroupResource
105+
: map ($ distroResource) [
80106
distroIndexPage
81107
, distroAllPage
82108
, distroPackages
@@ -109,10 +135,6 @@ distroFeature UserFeature{..}
109135
}
110136
}
111137

112-
maintainersGroup = \dpath -> case simpleParse =<< lookup "distro" dpath of
113-
Nothing -> return Nothing
114-
Just dname -> getMaintainersGroup adminGroup dname
115-
116138
textEnumDistros _ = fmap (toResponse . intercalate ", " . map display) (queryState distrosState EnumerateDistros)
117139
textDistroPkgs dpath = withDistroPath dpath $ \dname pkgs -> do
118140
let pkglines = map (\(name, info) -> display name ++ " at " ++ display (distroVersion info) ++ ": " ++ distroUrl info) pkgs
@@ -124,15 +146,15 @@ distroFeature UserFeature{..}
124146
-- result: see-other uri, or an error: not authenticated or not found (todo)
125147
distroDelete dpath =
126148
withDistroNamePath dpath $ \distro -> do
127-
guardAuthorised_ [InGroup adminGroup] --TODO: use the per-distro maintainer groups
149+
guardAuthorised_ [InGroup adminGroup]
128150
-- should also check for existence here of distro here
129151
void $ updateState distrosState $ RemoveDistro distro
130152
seeOther "/distros/" (toResponse ())
131153

132154
-- result: ok response or not-found error
133155
distroPackageDelete dpath =
134156
withDistroPackagePath dpath $ \dname pkgname info -> do
135-
guardAuthorised_ [AnyKnownUser] --TODO: use the per-distro maintainer groups
157+
guardAuthorised_ [InGroup $ distroGroup dname]
136158
case info of
137159
Nothing -> notFound . toResponse $ "Package not found for " ++ display pkgname
138160
Just {} -> do
@@ -142,30 +164,30 @@ distroFeature UserFeature{..}
142164
-- result: see-other response, or an error: not authenticated or not found (todo)
143165
distroPackagePut dpath =
144166
withDistroPackagePath dpath $ \dname pkgname _ -> lookPackageInfo $ \newPkgInfo -> do
145-
guardAuthorised_ [AnyKnownUser] --TODO: use the per-distro maintainer groups
167+
guardAuthorised_ [InGroup $ distroGroup dname]
146168
void $ updateState distrosState $ AddPackage dname pkgname newPkgInfo
147169
seeOther ("/distro/" ++ display dname ++ "/" ++ display pkgname) $ toResponse "Ok!"
148170

149171
-- result: see-other response, or an error: not authentcated or bad request
150172
distroPostNew _ =
151173
lookDistroName $ \dname -> do
152-
guardAuthorised_ [AnyKnownUser] --TODO: use the per-distro maintainer groups
174+
guardAuthorised_ [InGroup adminGroup]
153175
success <- updateState distrosState $ AddDistro dname
154176
if success
155177
then seeOther ("/distro/" ++ display dname) $ toResponse "Ok!"
156178
else badRequest $ toResponse "Selected distribution name is already in use"
157179

158180
distroPutNew dpath =
159181
withDistroNamePath dpath $ \dname -> do
160-
guardAuthorised_ [AnyKnownUser] --TODO: use the per-distro maintainer groups
182+
guardAuthorised_ [InGroup adminGroup]
161183
_success <- updateState distrosState $ AddDistro dname
162184
-- it doesn't matter if it exists already or not
163185
ok $ toResponse "Ok!"
164186

165187
-- result: ok repsonse or not-found error
166188
distroPackageListPut dpath =
167189
withDistroPath dpath $ \dname _pkgs -> do
168-
guardAuthorised_ [AnyKnownUser] --TODO: use the per-distro maintainer groups
190+
guardAuthorised_ [InGroup $ distroGroup dname]
169191
lookCSVFile $ \csv ->
170192
case csvToPackageList csv of
171193
Left msg ->
@@ -216,21 +238,6 @@ distroFeature UserFeature{..}
216238
Just distro -> func distro
217239
_ -> badRequest $ toResponse "Not a valid distro name"
218240

219-
getMaintainersGroup :: UserGroup -> DistroName -> IO (Maybe UserGroup)
220-
getMaintainersGroup admins dname = do
221-
isDist <- queryState distrosState (IsDistribution dname)
222-
case isDist of
223-
False -> return Nothing
224-
True -> return . Just $ UserGroup
225-
{ groupDesc = maintainerGroupDescription dname
226-
, queryUserGroup = queryState distrosState $ GetDistroMaintainers dname
227-
, addUserToGroup = updateState distrosState . AddDistroMaintainer dname
228-
, removeUserFromGroup = updateState distrosState . RemoveDistroMaintainer dname
229-
, groupsAllowedToAdd = [admins]
230-
, groupsAllowedToDelete = [admins]
231-
}
232-
233-
234241
maintainerGroupDescription :: DistroName -> GroupDescription
235242
maintainerGroupDescription dname = nullDescription
236243
{ groupTitle = "Maintainers"
@@ -260,6 +267,7 @@ csvToPackageList (CSVFile records)
260267
fromRecord [packageStr, versionStr, uri]
261268
| Just package <- simpleParse packageStr
262269
, Just version <- simpleParse versionStr
270+
, T.pack "https:" `T.isPrefixOf` T.pack uri
263271
= return (package, DistroPackageInfo version uri)
264-
fromRecord rec
265-
= Left $ "Invalid distro package entry: " ++ show rec
272+
fromRecord record
273+
= Left $ "Invalid distro package entry: " ++ show record

src/Distribution/Server/Features/Distro/Types.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
module Distribution.Server.Features.Distro.Types where
1010

11+
import Distribution.Server.Framework (FromReqURI(..))
1112
import Distribution.Server.Framework.Instances ()
1213
import Distribution.Server.Framework.MemSize
1314
import Distribution.Server.Users.State()
@@ -22,6 +23,7 @@ import Distribution.Package
2223
import Distribution.Pretty (Pretty(..))
2324
import Distribution.Parsec (Parsec(..))
2425
import qualified Distribution.Compat.CharParsing as P
26+
import Distribution.Text (simpleParse)
2527

2628
import qualified Text.PrettyPrint as Disp
2729
import qualified Data.Char as Char
@@ -39,6 +41,9 @@ instance Pretty DistroName where
3941
instance Parsec DistroName where
4042
parsec = DistroName <$> P.munch1 (\c -> Char.isAlphaNum c || c `elem` "-_()[]{}=$,;")
4143

44+
instance FromReqURI DistroName where
45+
fromReqURI = simpleParse
46+
4247
-- | Listing of known distributions and their maintainers
4348
data Distributions = Distributions {
4449
nameMap :: !(Map.Map DistroName UserIdSet)

0 commit comments

Comments
 (0)