Firstly, let us generate some set of random circles with findPoints from this answer
findPoints = Compile[{{n, _Integer}, {low, _Real}, {high, _Real}, {minD, _Real}}, Block[{data = RandomReal[{low, high}, {1, 2}], k = 1, rv, temp}, While[k < n, rv = RandomReal[{low, high}, 2]; temp = Transpose[Transpose[data] - rv]; If[Min[Sqrt[(#.#)] & /@ temp] > minD, data = Join[data, {rv}]; k++;];]; data]]; npts = 150; r = 0.03; minD = 2.2 r; low = 0; high = 1; pts = findPoints[npts, low, high, minD]; g2d = Graphics[{FaceForm@Lighter[Blue, 0.8], EdgeForm@Directive[Thickness[0.004], Black], Disk[#, r] & /@ pts}, PlotRange -> {{low, high}, {low, high}}, Background -> Lighter@Blue]

Method 1: Texture
We can simply use this graphics as a texture of the cube
pad = 0.1; coords = Tuples[{0, 1}, 3]; cube = Polygon[{{1, 3, 7, 5}, {1, 5, 6, 2}, {5, 7, 8, 6}, {7, 3, 4, 8}, {3, 1, 2, 4}, {6, 8, 4, 2}}]; vtc = pad + (1 - 2 pad) coords[[;; , {1, 3}]]; Graphics3D[{Texture[g2d], GraphicsComplex[coords, cube, VertexTextureCoordinates -> vtc]}, Lighting -> "Neutral", Boxed -> False, ImageSize -> 500]

Method 2: MeshRegion
I'm appreciate many upvotes so I want to expand my answer and add a more general approach. Mathematica has very powerful (and still very limited) region functions.
Let's try to use some interesting 2D mask:
mask = BoundaryDiscretizeRegion[#, {{0, 1}, {0, 1}}, MaxCellMeasure -> {1 -> .02}] &@ ImplicitRegion[ 0.1 < x < 0.9 && 0.1 < y < 0.9 + 0.05 Sin[20 x], {x, y}]; r2d = DiscretizeGraphics[g2d, MaxCellMeasure -> {1 -> .01}, PlotRange -> All]; inside = RegionIntersection[r2d, mask]

Then I find the edge and points on the edge. Unfortunately RegionIntersection doesn't work with lines and points. Here is workaround
edge = DiscretizeRegion@*Line@*Intersection @@ Round[{Sort /@ MeshPrimitives[RegionIntersection[r2d, mask], 1][[;; , 1]], Sort /@ MeshPrimitives[RegionDifference[r2d, mask], 1][[;; , 1]]}, .0001]; points = DiscretizeRegion@*Point@*Intersection @@ Round[{MeshPrimitives[RegionDifference[r2d, mask], 0][[;; , 1]], MeshPrimitives[RegionDifference[mask, r2d], 0][[;; , 1]]}, .0001];
Then I want to make RegionProduct to create 3D regions from corresponding 2D regions. I also have to use hand-written workaround
regionProduct[reg_, join_: True, y1_: 0, y2_: 1] := Module[{n = MeshCellCount[reg, 0]}, MeshRegion[Join @@ (ArrayFlatten@{{#[[;; , ;; 1]], #2, #[[;; , 2 ;;]]}} &[ MeshCoordinates@reg, #] & /@ {y1, y2}), {MeshCells[reg, _], MeshCells[reg, _] /. p : {__Integer} :> p + n, If[join, MeshCells[reg, _] /. {(Polygon | Line)[ p_] :> (Polygon@Join[#, Reverse[#, 2] + n, 2] &@ Partition[p, 2, 1, 1]), Point[p_] :> Line@{p, p + n}}, ## &[]]}]]; mask3d = regionProduct@mask; inside3d = regionProduct[inside, False]; edge3d = regionProduct@edge; points3d = regionProduct@points;
The result is impressive
toGC[reg_, dim_] := GraphicsComplex[MeshCoordinates@reg, MeshCells[reg, dim]]; Graphics3D[{FaceForm@Lighter[Blue, 0.7], toGC[inside3d, 2], EdgeForm[], toGC[edge3d, 2], toGC[points3d, 1], Lighter@Blue, GeometricTransformation[toGC[mask3d, 2], ScalingTransform[0.999 {1, 1, 1}, RegionCentroid@mask3d]]}, Lighting -> "Neutral", Boxed -> False]

Also with transparency:
Graphics3D[{FaceForm@Lighter[Blue, 0.7], toGC[regionProduct[RegionBoundary@inside, False], 1], EdgeForm[], toGC[regionProduct@inside, 2], toGC[edge3d, 2], toGC[points3d, 1], Blue, Opacity[0.03], GeometricTransformation[toGC[mask3d, 2], ScalingTransform[0.999 {1, 1, 1} #, RegionCentroid@mask3d] & /@ Range[0, 1, 0.01]]}, Lighting -> "Neutral", Boxed -> False, BaseStyle -> {RenderingOptions -> {"DepthPeelingLayers" -> 100}}]

I hope future versions will do it more automatically.